react-x11 2.52.0 → 2.53.0
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/README.md +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
package/docs/styling.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Styling
|
|
2
2
|
|
|
3
3
|
Style lives in one place: the `style` prop. A style property passed flat is
|
|
4
|
-
an error in development that names the fix
|
|
5
|
-
unrecognised prop used to be.
|
|
4
|
+
an error in development that names the fix.
|
|
6
5
|
|
|
7
6
|
## The rule
|
|
8
7
|
|
|
@@ -18,19 +17,16 @@ One namespace per kind of thing, and no name in both:
|
|
|
18
17
|
CSS.
|
|
19
18
|
|
|
20
19
|
`style` takes an object or a nested array, flattened left-to-right with
|
|
21
|
-
falsy entries skipped. That
|
|
22
|
-
|
|
20
|
+
falsy entries skipped. That replaces the **selector** cascade: precedence is
|
|
21
|
+
written at the call site instead of resolved by specificity.
|
|
23
22
|
|
|
24
23
|
```jsx
|
|
25
24
|
<box style={[s.card, isWide && s.wide, { backgroundColor: theme.panel }]} />
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_matching a rule against a tree_; what remains is a handful of properties
|
|
32
|
-
that travel down it, which is the part that makes a theme, a caption block
|
|
33
|
-
or a dimmed row expressible at all.
|
|
27
|
+
Nothing matches a rule against the tree, but inheritance does happen: a
|
|
28
|
+
handful of properties travel down it — see
|
|
29
|
+
[Inheritance](#inheritance-the-ink-the-face-and-the-size).
|
|
34
30
|
|
|
35
31
|
## Lengths and shorthands
|
|
36
32
|
|
|
@@ -40,60 +36,17 @@ value, and the display scale turns either into device pixels (see
|
|
|
40
36
|
mean what they mean in CSS; a border's or an outline's width also takes
|
|
41
37
|
`'thin'`, `'medium'` and `'thick'` (1, 3 and 5).
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
A **percentage on `fontSize`** is of the inherited size, as `em` is. A
|
|
55
|
-
**percentage `borderRadius`** is of the box's shorter side: a corner here is
|
|
56
|
-
a circle, so `'50%'` is a circle on a square and a pill on anything longer,
|
|
57
|
-
where CSS would draw each corner as an ellipse. Everywhere else a percentage
|
|
58
|
-
is the layout's, as before.
|
|
59
|
-
|
|
60
|
-
`calc()`, `min()`, `max()` and `clamp()` over any of these resolve to one
|
|
61
|
-
number where the style is — `'calc(1rem + 2px)'`, and fluid type as
|
|
62
|
-
`fontSize: 'clamp(14px, 1rem + 0.5vw, 20px)'`. A **percentage takes part
|
|
63
|
-
only alone** (`'calc(100% / 3)'`): yoga takes one unit per value, and what a
|
|
64
|
-
percentage is of is decided inside the layout, so `'calc(100% - 16px)'` is an
|
|
65
|
-
error that says so. Pad a `'100%'` box rather than subtracting from it, or
|
|
66
|
-
give the room to `flexGrow`.
|
|
67
|
-
|
|
68
|
-
`lineHeight` takes all of CSS's forms and comes to the multiple the text
|
|
69
|
-
engines take: `1.5`, `'150%'` and `'1.5em'` are that multiple, and a length
|
|
70
|
-
— `'24px'`, `'1.75rem'` — is divided by the node's own font size, which is
|
|
71
|
-
what makes a design's "16 on 24" the line it asks for.
|
|
72
|
-
|
|
73
|
-
What a relative length costs is the re-resolution, and only on the nodes that
|
|
74
|
-
wrote one: a node records what its style read — the inherited size, the
|
|
75
|
-
palette's, the window's — and is resolved again by the walk that already
|
|
76
|
-
carries that change (the text cascade, the theme walk, the size-query
|
|
77
|
-
registry). A style written in pixels reads none of them. Inside an
|
|
78
|
-
`'auto'`-sized window a viewport unit makes the content depend on the window
|
|
79
|
-
that depends on the content; like a size query, it settles where the two
|
|
80
|
-
agree, so size such a window or keep the viewport units out of it.
|
|
81
|
-
|
|
82
|
-
Some of CSS's units are **not read**, and a value in one is an error naming
|
|
83
|
-
the unit and why — `invalid width "2ex" in <box style>: ex is not read — it
|
|
84
|
-
needs the font's x-height`:
|
|
85
|
-
|
|
86
|
-
- `ex` and `ch`, which need font metrics nothing measures yet (`0.5em` is the
|
|
87
|
-
usual stand-in);
|
|
88
|
-
- the container units, `cqw` and the rest: a container's size is known only
|
|
89
|
-
after a layout pass, and a length in it would lay the tree out again on
|
|
90
|
-
every frame the container moves — a percentage of the parent, or an
|
|
91
|
-
`'@container'` block, says the same without that;
|
|
92
|
-
- the print units, `pt`, `mm`, `in`: lengths here are logical pixels, which
|
|
93
|
-
the display scale turns into device ones.
|
|
94
|
-
|
|
95
|
-
So is a second value where a property takes one. Yoga's own setters used to
|
|
96
|
-
`parseFloat` whatever they were handed, so `'2ex'` was quietly two pixels.
|
|
39
|
+
`null`, like `undefined`, is unset: `width: wide ? 240 : null` leaves the
|
|
40
|
+
width to the layout, and `''` is an unset colour. A value a property cannot
|
|
41
|
+
take (`width: 'hidden'`, `padding: 'auto'`, `flexDirection: 'sideways'`) or
|
|
42
|
+
a colour that does not parse (`'nonsense'`, `7`, `'#12'`) is an error at the
|
|
43
|
+
commit in development, naming the property — in a state block, a gradient
|
|
44
|
+
stop, a shadow, and a query block that does not match yet too. One that
|
|
45
|
+
gets past that — a `$token` resolving to it, or a production build — is
|
|
46
|
+
dropped where it is applied and reported, the way CSS drops a declaration it
|
|
47
|
+
cannot parse: the rest of the block still applies, and a dropped colour
|
|
48
|
+
paints no fill, stroke, gradient or shadow at all rather than half of one,
|
|
49
|
+
and the inherited ink for text.
|
|
97
50
|
|
|
98
51
|
```jsx
|
|
99
52
|
<box
|
|
@@ -131,11 +84,8 @@ A `$token` may stand for any one value inside them — `padding: '$gutter 0'`,
|
|
|
131
84
|
**The more specific property wins, wherever it is written.** A longhand
|
|
132
85
|
beats its shorthand, a side beats all four sides, and a pair beats all four
|
|
133
86
|
too: `paddingTop` beside `padding: '8px 16px'` is the top padding whether it
|
|
134
|
-
comes before or after it in the style.
|
|
135
|
-
|
|
136
|
-
A flattened style array has no order left to go by — a longhand and its
|
|
137
|
-
shorthand end up as two keys of one object — and "the specific one wins" is
|
|
138
|
-
the rule you can read off a style without knowing how it was assembled.
|
|
87
|
+
comes before or after it in the style. CSS goes by order instead; a
|
|
88
|
+
flattened style array has no order left to go by.
|
|
139
89
|
|
|
140
90
|
A part a shorthand leaves out is left unset rather than reset to CSS's
|
|
141
91
|
initial value, since a longhand written beside it would win anyway. Three
|
|
@@ -163,15 +113,67 @@ camelCase), and a misspelt name gets the nearest real ones. `background` and
|
|
|
163
113
|
by a shorthand reaches the longhands it expands into —
|
|
164
114
|
`transition: { border: 150 }` fades the `borderColor` a `border` set.
|
|
165
115
|
|
|
166
|
-
|
|
116
|
+
### Relative units
|
|
117
|
+
|
|
118
|
+
Every length also takes CSS's relative units, resolved for the node it is
|
|
119
|
+
written on into the same logical pixels:
|
|
120
|
+
|
|
121
|
+
| unit | one of it is | resolved again when |
|
|
122
|
+
| -------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
|
|
123
|
+
| `em` | the node's own font size — and on `fontSize` itself, the size it inherits, so `'1.5em'` is half again its parent's | an ancestor's font size changes |
|
|
124
|
+
| `rem` | the palette's `fontSize`: the size every label that names none is set in, 14 by default | the theme changes |
|
|
125
|
+
| `vw`, `vh`, `vmin`, `vmax` | a hundredth of the window the node is in — the `<window>` or `<popup>`, the size a `'@width >= 600'` block asks about | the window is resized |
|
|
126
|
+
|
|
127
|
+
`rem` is read from the nearest palette, the way a `$token` is: a
|
|
128
|
+
`<ThemeProvider value={{ fontSize: 12 }}>` around a dense panel re-bases it,
|
|
129
|
+
and an app porting web numbers that assume 16 sets `fontSize: 16` on its
|
|
130
|
+
theme.
|
|
131
|
+
|
|
132
|
+
A **percentage on `fontSize`** is of the inherited size, as `em` is. A
|
|
133
|
+
**percentage `borderRadius`** is of the box's shorter side: a corner here is
|
|
134
|
+
a circle, so `'50%'` is a circle on a square and a pill on anything longer,
|
|
135
|
+
where CSS would draw each corner as an ellipse. Everywhere else a percentage
|
|
136
|
+
is the layout's, as before.
|
|
137
|
+
|
|
138
|
+
`calc()`, `min()`, `max()` and `clamp()` over any of these resolve to one
|
|
139
|
+
number where the style is — `'calc(1rem + 2px)'`, and fluid type as
|
|
140
|
+
`fontSize: 'clamp(14px, 1rem + 0.5vw, 20px)'`. A **percentage takes part
|
|
141
|
+
only alone** (`'calc(100% / 3)'`): yoga takes one unit per value, and what a
|
|
142
|
+
percentage is of is decided inside the layout, so `'calc(100% - 16px)'` is an
|
|
143
|
+
error that says so. Pad a `'100%'` box rather than subtracting from it, or
|
|
144
|
+
give the room to `flexGrow`.
|
|
167
145
|
|
|
168
|
-
`
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
object because `minWidth`/`maxWidth` were already taken by yoga.
|
|
146
|
+
`lineHeight` takes all of CSS's forms and comes to the multiple the text
|
|
147
|
+
engines take: `1.5`, `'150%'` and `'1.5em'` are that multiple, and a length
|
|
148
|
+
— `'24px'`, `'1.75rem'` — is divided by the node's own font size, which is
|
|
149
|
+
what makes a design's "16 on 24" the line it asks for.
|
|
173
150
|
|
|
174
|
-
|
|
151
|
+
A relative length is re-resolved only on the nodes that wrote one, when what
|
|
152
|
+
it reads changes; a style written in pixels costs nothing. Inside an
|
|
153
|
+
`'auto'`-sized window a viewport unit makes the content depend on the window
|
|
154
|
+
that depends on the content; like a size query, it settles where the two
|
|
155
|
+
agree, so size such a window or keep the viewport units out of it.
|
|
156
|
+
|
|
157
|
+
Some of CSS's units are **not read**, and a value in one is an error naming
|
|
158
|
+
the unit and why — `invalid width "2ex" in <box style>: ex is not read — it
|
|
159
|
+
needs the font's x-height`:
|
|
160
|
+
|
|
161
|
+
- `ex` and `ch`, which need font metrics nothing measures yet (`0.5em` is the
|
|
162
|
+
usual stand-in);
|
|
163
|
+
- the container units, `cqw` and the rest: a container's size is known only
|
|
164
|
+
after a layout pass, and a length in it would lay the tree out again on
|
|
165
|
+
every frame the container moves — a percentage of the parent, or an
|
|
166
|
+
`'@container'` block, says the same without that;
|
|
167
|
+
- the print units, `pt`, `mm`, `in`: lengths here are logical pixels, which
|
|
168
|
+
the display scale turns into device ones.
|
|
169
|
+
|
|
170
|
+
So is a second value where a property takes one.
|
|
171
|
+
|
|
172
|
+
## What it fixes on `<window>`
|
|
173
|
+
|
|
174
|
+
`width`/`height` on a `<window>` are the real window's geometry — the user
|
|
175
|
+
can drag them — so they are props and never reach the layout, and the window
|
|
176
|
+
manager's size hints sit beside them, flat:
|
|
175
177
|
|
|
176
178
|
```jsx
|
|
177
179
|
<window
|
|
@@ -187,9 +189,8 @@ With the split, both workarounds go:
|
|
|
187
189
|
/>
|
|
188
190
|
```
|
|
189
191
|
|
|
190
|
-
`
|
|
191
|
-
|
|
192
|
-
creation attribute — the ntk contract is unchanged, only the call site is.
|
|
192
|
+
`style` is the root box, and its `backgroundColor` is also the window's own
|
|
193
|
+
background colour.
|
|
193
194
|
|
|
194
195
|
## Inline pseudo-states
|
|
195
196
|
|
|
@@ -206,12 +207,10 @@ The thing inline CSS cannot do, and the reason people keep a stylesheet:
|
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
`:hover`, `:focus-within`, `:focus`, `:focus-visible`, `:active`,
|
|
209
|
-
`:disabled`, `:drag-over`, `:dragging`.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
node; a state change recomputes one node's style and repaints. **No React
|
|
214
|
-
render.**
|
|
210
|
+
`:disabled`, `:drag-over`, `:dragging`. These are **node states, not
|
|
211
|
+
selectors**: each is something the node itself already knows, so there is no
|
|
212
|
+
specificity and no matching. A state change recomputes one node's style and
|
|
213
|
+
repaints — **no React render.**
|
|
215
214
|
|
|
216
215
|
Precedence is fixed and low-to-high: `:hover` → `:focus-within` → `:focus` →
|
|
217
216
|
`:focus-visible` → `:active` → `:disabled` → `:drag-over` → `:dragging`,
|
|
@@ -221,18 +220,13 @@ focus, and a drag in progress outranks all of the pointer and focus states.
|
|
|
221
220
|
Because the hover _path_ is the ancestor chain, hovering a child lights up an
|
|
222
221
|
ancestor's `:hover` block, exactly like CSS.
|
|
223
222
|
|
|
224
|
-
`:active` is a press, and
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
This is not decoration. A control acts on the release, so `:active` is the
|
|
233
|
-
only thing it can show while it is held, and a click held half a second is
|
|
234
|
-
half a second of silence without it. Give every interactive thing one — see
|
|
235
|
-
[components.md](components.md#the-press-state).
|
|
223
|
+
`:active` is a press, and follows the same chain, so the control draws the
|
|
224
|
+
press whichever of its parts — a label, a thumb — is under the pointer. It
|
|
225
|
+
is **live for the whole gesture**: it drops when the pointer leaves the
|
|
226
|
+
chain and comes back when it returns, so it always means "releasing now
|
|
227
|
+
activates this". A control acts on the release, so give every interactive
|
|
228
|
+
thing an `:active` look: it is the only thing it can show while it is held
|
|
229
|
+
([components.md](components.md#the-press-state)).
|
|
236
230
|
|
|
237
231
|
```jsx
|
|
238
232
|
<box
|
|
@@ -249,30 +243,25 @@ A `transition` on the same property is welcome — what matters is that the
|
|
|
249
243
|
change _starts_ on the press frame, not that it finishes there.
|
|
250
244
|
|
|
251
245
|
The keyboard has no press state: Space and Enter act on the down, so the
|
|
252
|
-
activation is immediate, but nothing draws held-ness
|
|
253
|
-
|
|
254
|
-
`DetectableAutoRepeat`, so a held key cannot be told from fast tapping.
|
|
246
|
+
activation is immediate, but nothing draws held-ness — on X11 a held key
|
|
247
|
+
cannot be told from fast tapping.
|
|
255
248
|
|
|
256
249
|
`:focus-visible` is focus that came from the keyboard — Tab, an arrow inside
|
|
257
250
|
a widget, `autoFocus`, `node.focus()`, a modal handing focus back as it
|
|
258
|
-
closes. A **press** sets `:focus` and not `:focus-visible`,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
either way in `:focus`.
|
|
251
|
+
closes. A **press** sets `:focus` and not `:focus-visible`, as in CSS: the
|
|
252
|
+
user knows where they clicked, and a ring on every click is noise, where a
|
|
253
|
+
ring on Tab is the only cue a keyboard user has. Put focus rings in
|
|
254
|
+
`:focus-visible` and colour changes that are welcome either way in `:focus`.
|
|
263
255
|
|
|
264
256
|
The exception is CSS's too: a press on a **text control** — `<textinput>`,
|
|
265
257
|
`<textarea>`, or a custom element reporting editable text — does set
|
|
266
|
-
`:focus-visible
|
|
267
|
-
that every keystroke from now on lands there
|
|
268
|
-
the ring is for.
|
|
258
|
+
`:focus-visible`, because what the click told the user is where the caret
|
|
259
|
+
went, not that every keystroke from now on lands there.
|
|
269
260
|
|
|
270
261
|
`:focus-within` is focus on this node **or inside it** — CSS's, and the
|
|
271
262
|
answer to "the row should light up while the field in it is being typed
|
|
272
|
-
into"
|
|
273
|
-
|
|
274
|
-
`<popup>` counts as inside the node it hangs off in the JSX tree, so a
|
|
275
|
-
`Select` with its menu open still reads as focused.
|
|
263
|
+
into". A `<popup>` counts as inside the node it hangs off in the JSX tree,
|
|
264
|
+
so a `Select` with its menu open still reads as focused.
|
|
276
265
|
|
|
277
266
|
```jsx
|
|
278
267
|
<box
|
|
@@ -293,13 +282,19 @@ that. `:dragging` is set on the source node for the duration of a drag.
|
|
|
293
282
|
See [drag-and-drop.md](drag-and-drop.md).
|
|
294
283
|
|
|
295
284
|
**State blocks may only set paint properties** (`backgroundColor`,
|
|
296
|
-
`borderColor` and the per-side `borderTopColor`/…,
|
|
297
|
-
`outlineWidth`, `outlineColor`,
|
|
298
|
-
`outlineOffset`, `opacity`, `color
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
285
|
+
`backgroundImage`, `borderColor` and the per-side `borderTopColor`/…,
|
|
286
|
+
`borderRadius`, `boxShadow`, `zIndex`, `outlineWidth`, `outlineColor`,
|
|
287
|
+
`outlineOffset`, `opacity`, `color`, and the `background` and `outline`
|
|
288
|
+
shorthands) — enforced at declaration time by `createStyles`, so that a
|
|
289
|
+
pointer move never reflows the tree. Anything that changes layout or what
|
|
290
|
+
renders stays in React state.
|
|
291
|
+
|
|
292
|
+
There are **no relational selectors** — no `:hover > child`, no sibling
|
|
293
|
+
combinator, no Tailwind-style `group`. A parent's `:hover` reaches its
|
|
294
|
+
children because `color` is
|
|
295
|
+
[inherited](#inheritance-the-ink-the-face-and-the-size), a child's hover
|
|
296
|
+
reaches its ancestors because the hover path is the ancestor chain, and a
|
|
297
|
+
sibling reacting to a sibling stays in React ([In components](#in-components)).
|
|
303
298
|
|
|
304
299
|
### The focus ring
|
|
305
300
|
|
|
@@ -312,8 +307,7 @@ for the keyboard (see `:focus-visible` above).
|
|
|
312
307
|
`outlineWidth`, `outlineColor` and `outlineOffset` override it, and they are
|
|
313
308
|
paint properties like any other: animatable, legal in a state block, and
|
|
314
309
|
painted **outside the border box** so switching one on cannot move the thing
|
|
315
|
-
it surrounds.
|
|
316
|
-
and the reason these are not in the layout vocabulary.
|
|
310
|
+
it surrounds.
|
|
317
311
|
|
|
318
312
|
```jsx
|
|
319
313
|
<box focusable /> {/* ring, for free */}
|
|
@@ -342,13 +336,11 @@ it mirrors that subtree and nothing above it. Rows run the other way, `flex-star
|
|
|
342
336
|
is the right-hand end, and every logical edge below swaps sides with it.
|
|
343
337
|
|
|
344
338
|
**The default comes from the locale.** An app started under
|
|
345
|
-
`LANG=ar_EG.UTF-8` is mirrored with no configuration,
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
with a language menu switches the whole UI with the `<ThemeProvider>` swap
|
|
351
|
-
it was already doing for colours:
|
|
339
|
+
`LANG=ar_EG.UTF-8` is mirrored with no configuration, as GTK and Qt apps
|
|
340
|
+
are. Every locale that is not written right to left answers `'ltr'`, so an
|
|
341
|
+
app that never thinks about this pays nothing. The palette carries it —
|
|
342
|
+
`theme.direction` — so an app with a language menu switches the whole UI with
|
|
343
|
+
the `<ThemeProvider>` swap it was already doing for colours:
|
|
352
344
|
|
|
353
345
|
```jsx
|
|
354
346
|
<ThemeProvider value={{ direction: settings.rtl ? 'rtl' : 'ltr' }}>
|
|
@@ -373,10 +365,8 @@ routes always agree under one.
|
|
|
373
365
|
| `borderStartColor` / `…EndColor` | `borderLeftColor` / `…Right…` | swapped |
|
|
374
366
|
|
|
375
367
|
**A logical edge wins over the physical one even in LTR**, the way CSS's
|
|
376
|
-
`padding-inline-start` wins over `padding-left
|
|
377
|
-
|
|
378
|
-
`EDGE_ALL` — the opposite way round from what the vertical shorthands
|
|
379
|
-
suggest, which is why it is pinned in a test.
|
|
368
|
+
`padding-inline-start` wins over `padding-left`, and the physical side wins
|
|
369
|
+
over all four sides.
|
|
380
370
|
|
|
381
371
|
Write layout in the logical pair and it is the same layout in both
|
|
382
372
|
directions. Use the physical one when you mean the screen: a drop shadow, a
|
|
@@ -392,8 +382,7 @@ resize handle in a fixed corner.
|
|
|
392
382
|
scrollbar's travel, Up/Down on every control.
|
|
393
383
|
- **The caret keys in a field.** Left and Right step through the _string_,
|
|
394
384
|
not across the screen, and so do Home and End — the editing model is
|
|
395
|
-
written in logical positions
|
|
396
|
-
text is its own question.
|
|
385
|
+
written in logical positions.
|
|
397
386
|
|
|
398
387
|
What **does**, besides the box tree: a vertical scrollbar moves to the left;
|
|
399
388
|
horizontal scrolling counts from the right-hand edge, so `scrollX: 0` still
|
|
@@ -407,18 +396,16 @@ screen edge; and the inside of a `<textinput>`/`<textarea>` — see below.
|
|
|
407
396
|
A field is laid out at its box's direction, exactly as a `<text>` is, and the
|
|
408
397
|
direction does two separate things there:
|
|
409
398
|
|
|
410
|
-
- **It is the base level the value is shaped at.**
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
**LTR** window. So the field says.
|
|
399
|
+
- **It is the base level the value is shaped at.** A run of neutral
|
|
400
|
+
characters — `"(1) 12:30"`, a filename, a lone bracket — resolves against
|
|
401
|
+
it rather than against the first strong character, so an Arabic word typed
|
|
402
|
+
into an English form in an **LTR** window does not reorder the line as
|
|
403
|
+
though it were Arabic.
|
|
416
404
|
- **It is the edge the text is against.** The value, the placeholder and the
|
|
417
405
|
caret start at the direction's start edge — the right-hand one under
|
|
418
406
|
`direction: 'rtl'` — and an unscrolled field still shows the _beginning_ of
|
|
419
407
|
its value, so a value too long for its box overflows towards the left.
|
|
420
|
-
Clicking, the caret, the selection bands and the scroll
|
|
421
|
-
placement, so they mirror together.
|
|
408
|
+
Clicking, the caret, the selection bands and the scroll mirror together.
|
|
422
409
|
|
|
423
410
|
`textAlign` works on a field too: `'start'` (the default) is the base
|
|
424
411
|
direction's start edge, and `'center'`/`'end'` do what they say while the
|
|
@@ -427,7 +414,7 @@ whatever the alignment says, because that is what it is scrolled from.
|
|
|
427
414
|
|
|
428
415
|
The text's _own_ direction is not consulted: a Latin value in an RTL field is
|
|
429
416
|
placed at the field's right edge, the way `<input dir="rtl">` places one, and
|
|
430
|
-
there is no `dir="auto"` equivalent
|
|
417
|
+
there is no `dir="auto"` equivalent.
|
|
431
418
|
|
|
432
419
|
## Per-side borders
|
|
433
420
|
|
|
@@ -449,28 +436,21 @@ widths are layout — yoga sees each edge, so the bar above insets content on
|
|
|
449
436
|
the left only — and the colours are paint, legal in a state block like
|
|
450
437
|
`borderColor` itself.
|
|
451
438
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
node per row into layout and paint, and scatters what is really one style
|
|
456
|
-
value ("the table's border colour") across the tree.
|
|
439
|
+
Build a rule or an accent edge this way — a blockquote's bar, a table's row
|
|
440
|
+
separators (`borderBottomWidth: 1` on each row), a tab's underline — rather
|
|
441
|
+
than out of 1px `<box>`es, which pay a node per row in layout and paint.
|
|
457
442
|
|
|
458
|
-
Two
|
|
443
|
+
Two limits:
|
|
459
444
|
|
|
460
445
|
- **`borderRadius` requires uniform borders** — same width and colour on
|
|
461
446
|
all four sides. A non-uniform border paints square, ignores the radius,
|
|
462
|
-
and says so once in development
|
|
463
|
-
nothing real.
|
|
447
|
+
and says so once in development.
|
|
464
448
|
- Corners between two painted sides of different colours are square and
|
|
465
449
|
deterministic: top and bottom span the box's full width, left and right
|
|
466
|
-
run between them
|
|
467
|
-
bars and rules can tell the difference.
|
|
450
|
+
run between them, where CSS mitres the corner diagonally.
|
|
468
451
|
|
|
469
452
|
## Gradients and shadows
|
|
470
453
|
|
|
471
|
-
The two decorations a UI asks for after colour and radius, and the two that
|
|
472
|
-
used to mean giving up on `<box>` and drawing the panel by hand:
|
|
473
|
-
|
|
474
454
|
```jsx
|
|
475
455
|
<box
|
|
476
456
|
style={{
|
|
@@ -488,9 +468,8 @@ used to mean giving up on `<box>` and drawing the panel by hand:
|
|
|
488
468
|
Both are **paint** properties: legal in a state block, and outside layout
|
|
489
469
|
entirely — a gradient fills the box the layout already decided on, and a
|
|
490
470
|
shadow is drawn beyond it and moves nothing. Both are written in CSS's
|
|
491
|
-
spelling,
|
|
492
|
-
|
|
493
|
-
keeps a themed decoration in a hoisted style.
|
|
471
|
+
spelling, and a `$token` resolves **inside** them as well as as a whole
|
|
472
|
+
value, which is what keeps a themed decoration in a hoisted style.
|
|
494
473
|
|
|
495
474
|
### `backgroundImage`
|
|
496
475
|
|
|
@@ -507,64 +486,39 @@ against the node's own box so it works wherever the node lands.
|
|
|
507
486
|
|
|
508
487
|
Only linear gradients exist. A radial or conic one is a `<canvas onDraw>`
|
|
509
488
|
away (`createRadialGradient` draws on every backend but Windows', and
|
|
510
|
-
ntk's context has `createConicalGradient`)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
a colour — but a **rounded** one is not. ntk's rounded-rect fast path (cached
|
|
517
|
-
corner glyphs plus `FillRectangles`) only takes a solid colour, so a rounded
|
|
518
|
-
gradient falls back to a coverage mask rasterized on the client and uploaded
|
|
519
|
-
per fill: about 4 KB per box per repaint. Damage bounding means that is only
|
|
520
|
-
paid where something changed, but a list of rounded gradient rows is the one
|
|
521
|
-
shape to think twice about — see the `shapes: 24 gradient+shadow cards`
|
|
522
|
-
scenario in `npm run bench`, which prices it.
|
|
489
|
+
ntk's context has `createConicalGradient`).
|
|
490
|
+
|
|
491
|
+
On X11 a **square** gradient is as cheap as a colour, but a **rounded** one
|
|
492
|
+
is rasterized on the client and uploaded per fill — about 4 KB per box per
|
|
493
|
+
repaint, paid only where something changed. A list of rounded gradient rows
|
|
494
|
+
is the one shape to think twice about.
|
|
523
495
|
|
|
524
496
|
### `boxShadow`
|
|
525
497
|
|
|
526
498
|
`<x> <y> [blur] [spread] [colour]`, comma-separated for several, painted
|
|
527
499
|
first-on-top like CSS's. The colour may be left out, which means the node's
|
|
528
500
|
own `color` — [`currentColor`](#currentcolor), which may be written out
|
|
529
|
-
too. A blurred shadow is a real gaussian
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
On macOS and Windows, which have no X server to bake one on, the shadow is
|
|
534
|
-
the 2d context's own `shadowBlur`, and the context draws a rounded rect's
|
|
535
|
-
shadow from a tile it makes once and stretches — see
|
|
536
|
-
[macos.md](macos.md#a-shadow-from-a-tile). The rest of this section is the
|
|
537
|
-
X11 route.
|
|
538
|
-
|
|
539
|
-
The blur is **baked into the cached surface's pixels**, in two separable 1-D
|
|
540
|
-
passes, rather than left as a filter on its picture. That is not an internal
|
|
541
|
-
detail: XRender applies a picture's filter on every composite, so a shadow
|
|
542
|
-
whose blur rode on the picture paid its whole kernel again every frame it was
|
|
543
|
-
drawn — a card-sized coverage surface at a wide blur is hundreds of millions
|
|
544
|
-
of multiply-accumulates, in the server, per shadow, per repaint. Baked, a
|
|
545
|
-
repaint composites a plain mask however wide the blur is, and the convolution
|
|
546
|
-
runs once per distinct geometry.
|
|
501
|
+
too. A blurred shadow is a real gaussian, computed once per distinct size,
|
|
502
|
+
radius and blur and cached, so a list of identical cards renders one and
|
|
503
|
+
composites it many times, and a repaint costs the same however wide the
|
|
504
|
+
blur. On macOS and Windows it is the 2D context's own `shadowBlur`.
|
|
547
505
|
|
|
548
506
|
What it does not do, and why:
|
|
549
507
|
|
|
550
|
-
- **`inset` throws.** An inner shadow is a different drawing and
|
|
551
|
-
|
|
552
|
-
|
|
508
|
+
- **`inset` throws.** An inner shadow is a different drawing, and painting an
|
|
509
|
+
outer one where an inner was asked for is a bug with no visible cause. An
|
|
510
|
+
inset border or a `<canvas>` says it today.
|
|
553
511
|
- **Ignored on `<window>` and `<popup>`** (with a warning in development). A
|
|
554
512
|
shadow is painted outside the box, and a toplevel owns no pixels there —
|
|
555
|
-
a real one needs the window to carry a translucent margin of its own
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
`borderRadius` plus a border is what reads as raised today.
|
|
513
|
+
a real one needs the window to carry a translucent margin of its own.
|
|
514
|
+
Put the shadow on a `<box>` inside the window; for a floating menu, the
|
|
515
|
+
popup's own `borderRadius` plus a border is what reads as raised.
|
|
559
516
|
- **Neither transitions.** Both are several numbers and a colour in one
|
|
560
|
-
string
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
A shadow
|
|
564
|
-
|
|
565
|
-
spread and the blur's tail — and the frame that _removes_ one claims where
|
|
566
|
-
it was. That is the renderer's business, not the application's, but it is
|
|
567
|
-
the reason a shadow is not free the way a colour is: prefer one shadow on
|
|
517
|
+
string; they snap. A card that wants to rise on hover animates the
|
|
518
|
+
`backgroundColor` or the border beside them.
|
|
519
|
+
|
|
520
|
+
A shadow inks pixels the node does not own, so it widens what the node
|
|
521
|
+
repaints — its offset, its spread and the blur's tail. Prefer one shadow on
|
|
568
522
|
the card to one on every row inside it.
|
|
569
523
|
|
|
570
524
|
## Opacity
|
|
@@ -598,11 +552,6 @@ faded and released when it is opaque again. `0` draws nothing and costs
|
|
|
598
552
|
nothing — and the node is still laid out and still hit, as in CSS. A value
|
|
599
553
|
outside 0 to 1 is clamped; one that is not a number throws in development.
|
|
600
554
|
|
|
601
|
-
On the Cocoa backend a faded box is drawn into the window's bitmap like this
|
|
602
|
-
rather than lifted onto a layer of its own (docs/macos.md): a box and its
|
|
603
|
-
content on separate layers would fade one over the other, which is the thing
|
|
604
|
-
`opacity` exists not to do.
|
|
605
|
-
|
|
606
555
|
## Measuring text to its letters
|
|
607
556
|
|
|
608
557
|
```jsx
|
|
@@ -616,18 +565,12 @@ alphabetic`: the `<text>`'s box becomes the capitals down to the last
|
|
|
616
565
|
baseline, so padding around a label is measured from the letters and
|
|
617
566
|
centring centres what you can see. Default `'none'`.
|
|
618
567
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
12 and 12.
|
|
626
|
-
|
|
627
|
-
**`lineHeight` is not an alternative.** It is a multiplier over the natural
|
|
628
|
-
line box and the leading still splits evenly above and below, so it moves
|
|
629
|
-
both edges by the same amount — it can change how much space there is, never
|
|
630
|
-
how it is balanced.
|
|
568
|
+
**A line box is not the text**: the space over a capital differs from the
|
|
569
|
+
space under a baseline by `(ascent - capHeight) - descent`, a property of
|
|
570
|
+
the typeface, so an untrimmed label is only optically centred by luck.
|
|
571
|
+
**`lineHeight` is not an alternative**: it is a multiplier over the natural
|
|
572
|
+
line box, and the leading still splits evenly above and below, so it changes
|
|
573
|
+
how much space there is, never how it is balanced.
|
|
631
574
|
|
|
632
575
|
Two things worth knowing:
|
|
633
576
|
|
|
@@ -639,11 +582,9 @@ Two things worth knowing:
|
|
|
639
582
|
|
|
640
583
|
**The built-in widgets set it on every label**, which is why `paddingY` in a
|
|
641
584
|
palette is bigger than a CSS padding for the same look — it is the space you
|
|
642
|
-
see, not the space plus whatever the ascent left over. Which way
|
|
643
|
-
off changes with the face
|
|
644
|
-
|
|
645
|
-
Helvetica leaves 0.7 against 3.2 and it rides high. A widget cannot correct
|
|
646
|
-
for that, because it does not know the face it will be drawn in.
|
|
585
|
+
see, not the space plus whatever the ascent left over. Which way an untrimmed
|
|
586
|
+
label is off changes with the face (it rides low in SF NS and high in
|
|
587
|
+
Helvetica), and a widget does not know the face it will be drawn in.
|
|
647
588
|
|
|
648
589
|
A glyph drawn as text — a check mark, a submenu arrow, an icon — is centred
|
|
649
590
|
on its own middle rather than sitting on a baseline, so trimming its box
|
|
@@ -687,16 +628,14 @@ that a paragraph is made of, and nothing hangs out of a stack of them.
|
|
|
687
628
|
`textWrap: 'nowrap'` is CSS's, and it is what separates a cell from a
|
|
688
629
|
paragraph. A `<text>` measures height-for-width: hand it a narrow box and it
|
|
689
630
|
wraps to fit, which is right for prose and wrong for a row of a fixed height —
|
|
690
|
-
a date that wraps
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
to do with. Default `'wrap'`.
|
|
631
|
+
a date that wraps is sliced off top and bottom and spills over the rows
|
|
632
|
+
either side. `'nowrap'` measures at unbounded width, so the overflow is
|
|
633
|
+
horizontal, which `overflow: 'hidden'` on the box around it clips. Default
|
|
634
|
+
`'wrap'`.
|
|
695
635
|
|
|
696
|
-
A date
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
reader has no way to tell which they are looking at:
|
|
636
|
+
A date always fits, so clipping it would be a bug rather than a design. A
|
|
637
|
+
**name** does not: clipped, a truncated file name and a short one look
|
|
638
|
+
equally complete.
|
|
700
639
|
|
|
701
640
|
```jsx
|
|
702
641
|
<text style={{ textWrap: 'nowrap', textOverflow: 'ellipsis' }}>
|
|
@@ -717,12 +656,10 @@ a substring —
|
|
|
717
656
|
- it cuts the **visually** last run rather than the logically last one, so a
|
|
718
657
|
right-to-left line ends on its left.
|
|
719
658
|
|
|
720
|
-
`maxLines` is the other half — CSS
|
|
721
|
-
|
|
722
|
-
the
|
|
723
|
-
|
|
724
|
-
cap are dropped before the box is measured, so the height is the kept lines'
|
|
725
|
-
and not the whole paragraph's.
|
|
659
|
+
`maxLines` is the other half — CSS's `-webkit-line-clamp`. A three-line blurb
|
|
660
|
+
that ends in a `…` is `{ maxLines: 3, textOverflow: 'ellipsis' }`; the lines
|
|
661
|
+
past the cap are dropped before the box is measured, so the height is the
|
|
662
|
+
kept lines' and not the whole paragraph's.
|
|
726
663
|
|
|
727
664
|
**`textOverflow: 'ellipsis'` on its own means one line.** Eliding happens off
|
|
728
665
|
a line count — the mark stands for the lines that were dropped — so an
|
|
@@ -732,8 +669,8 @@ ask for two or three.
|
|
|
732
669
|
|
|
733
670
|
### What `nowrap` and `ellipsis` do together
|
|
734
671
|
|
|
735
|
-
They
|
|
736
|
-
|
|
672
|
+
They change what the `<text>` reports to layout, which is worth knowing
|
|
673
|
+
before a row moves under you.
|
|
737
674
|
|
|
738
675
|
A clipping `nowrap` label is measured at unbounded width, so it tells its
|
|
739
676
|
container it needs the **whole string** — and CSS's automatic minimum size
|
|
@@ -741,21 +678,15 @@ turns that into a floor. It cannot be squeezed, so a long name pushes the
|
|
|
741
678
|
column next to it out of the row and the overflow is dealt with somewhere
|
|
742
679
|
above.
|
|
743
680
|
|
|
744
|
-
An eliding one is measured against the width on offer,
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
should say so rather than making every other column narrower to avoid it.
|
|
750
|
-
|
|
751
|
-
`<Table>` sets `nowrap` and `ellipsis` on every cell and header for that
|
|
752
|
-
reason.
|
|
681
|
+
An eliding one is measured against the width on offer, so its floor is small
|
|
682
|
+
and it **gives way**: it takes the room that is left, shows `Applicati…`,
|
|
683
|
+
and the column beside it keeps its width. That is the behaviour a table
|
|
684
|
+
wants, and `<Table>` sets `nowrap` and `ellipsis` on every cell and header
|
|
685
|
+
for that reason.
|
|
753
686
|
|
|
754
687
|
What is truncated is a fact about the pixels and nothing else. A `<text>` that
|
|
755
688
|
was elided still reports the **whole** string as its accessible name, and the
|
|
756
|
-
caret and selection indices still index into the whole string
|
|
757
|
-
reader that read `Application Sup…` would be reading the layout instead of the
|
|
758
|
-
content.
|
|
689
|
+
caret and selection indices still index into the whole string.
|
|
759
690
|
|
|
760
691
|
## Inheritance: the ink, the face and the size
|
|
761
692
|
|
|
@@ -810,12 +741,10 @@ its icon follow.
|
|
|
810
741
|
</box>
|
|
811
742
|
```
|
|
812
743
|
|
|
813
|
-
Nothing about that costs a layout pass
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
_does_ re-measure, and it can only come from a React commit, a size query or
|
|
818
|
-
a theme.
|
|
744
|
+
Nothing about that costs a layout pass: a state block sets paint and `color`
|
|
745
|
+
only, so the only inherited property a pointer can move is the ink, which
|
|
746
|
+
repaints without re-measuring. A `fontSize` change _does_ re-measure, and it
|
|
747
|
+
can only come from a React commit, a size query or a theme.
|
|
819
748
|
|
|
820
749
|
What does **not** inherit: `textAlign`, `lineHeight`, `textWrap`,
|
|
821
750
|
`textOverflow`, `maxLines` and `textBoxTrim`. CSS inherits the first two;
|
|
@@ -829,11 +758,10 @@ drawing rather than a letter, so it takes its default from the palette's
|
|
|
829
758
|
development, naming the property; where one gets past that — a `$token`, or
|
|
830
759
|
a production build — the text sets as though it had not been written, the way
|
|
831
760
|
CSS inherits past a declaration it cannot parse. Numbers may be numeric
|
|
832
|
-
strings (`fontSize: '20'`),
|
|
761
|
+
strings (`fontSize: '20'`), and a size is a
|
|
833
762
|
[length](#lengths-and-shorthands) like any other: `fontSize: '20px'` is 20,
|
|
834
763
|
and `'1.5em'` or `'150%'` is half again the size inherited. A `lineHeight`
|
|
835
|
-
in a length — `'24px'` — is that multiple of the node's own size.
|
|
836
|
-
size in px set no text at all and the others threw from inside a frame.
|
|
764
|
+
in a length — `'24px'` — is that multiple of the node's own size.
|
|
837
765
|
|
|
838
766
|
### `currentColor`
|
|
839
767
|
|
|
@@ -859,9 +787,8 @@ It stays the keyword in the node's style, which is CSS's computed value, and
|
|
|
859
787
|
becomes a colour where it is painted, so it follows the ink wherever the ink
|
|
860
788
|
moves: a `:hover` on a row above, a theme swap, a `color` transition frame by
|
|
861
789
|
frame. A node painting in it repaints when its ink moves, bounded to that
|
|
862
|
-
node
|
|
863
|
-
|
|
864
|
-
under the row inherits the ink like any other child and repaints with it.
|
|
790
|
+
node. A `<popup>` written under the row inherits the ink like any other
|
|
791
|
+
child and repaints with it.
|
|
865
792
|
|
|
866
793
|
Two places it cannot simply follow:
|
|
867
794
|
|
|
@@ -946,28 +873,23 @@ that is merely _previewing_ files does not — it would quietly change which
|
|
|
946
873
|
face draws the bullets and the curly quotes in its own UI.
|
|
947
874
|
|
|
948
875
|
Both verbs read a given file once per connection, however often they are
|
|
949
|
-
called:
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
open. This matters more than it looks — a second copy of a font is a second
|
|
953
|
-
glyph cache and a second server-side glyphset, and a node built against one
|
|
954
|
-
copy cannot be painted by the other.
|
|
876
|
+
called: `useFont` in a component that re-renders sixty times a second parses
|
|
877
|
+
nothing, and `loadFont` after `openFont` on the same path registers the face
|
|
878
|
+
already open.
|
|
955
879
|
|
|
956
880
|
`font` is ntk's `Font`, which has more on it than the four members above
|
|
957
881
|
([ntk's docs/fonts.md](https://github.com/sidorares/ntk/blob/master/docs/fonts.md)). If you need
|
|
958
882
|
anything else of ntk's — `Path2D`, `Image`, `Surface` — **import it from
|
|
959
883
|
`react-x11/ntk`, never from `ntk`**, and never declare `ntk` as a dependency
|
|
960
884
|
of your own. Two copies in one process are two font caches and two glyph
|
|
961
|
-
atlases,
|
|
962
|
-
its cause.
|
|
885
|
+
atlases, and a node built against one copy cannot be painted by the other.
|
|
963
886
|
|
|
964
887
|
## `createStyles`
|
|
965
888
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
declaration instead of a silent no-op.
|
|
889
|
+
A hoisted style object keeps its identity, so `applyProps` skips the whole
|
|
890
|
+
update with a `===` check. `createStyles` also validates keys, which a bare
|
|
891
|
+
object literal cannot: an unknown style property is an error at declaration
|
|
892
|
+
instead of a silent no-op.
|
|
971
893
|
|
|
972
894
|
## `flattenStyle`
|
|
973
895
|
|
|
@@ -996,8 +918,8 @@ node-local state block can cross that.
|
|
|
996
918
|
A component's own props are never style. `ProgressBar` takes `color`,
|
|
997
919
|
`Dialog` takes `width`/`height` — a dialog is a real popup window and needs
|
|
998
920
|
its geometry up front — and `ContextMenu` takes `fontSize` because it
|
|
999
|
-
measures labels with it. `Select`
|
|
1000
|
-
|
|
921
|
+
measures labels with it. A `Select`'s or a `Slider`'s width is
|
|
922
|
+
`style={{ width }}`.
|
|
1001
923
|
|
|
1002
924
|
## Theme tokens
|
|
1003
925
|
|
|
@@ -1017,10 +939,9 @@ const s = createStyles({
|
|
|
1017
939
|
</window>;
|
|
1018
940
|
```
|
|
1019
941
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
paints (`style={[s.card, { backgroundColor: theme.panel }]}`).
|
|
942
|
+
A **hoisted** style — declared once, outside render, with no access to React
|
|
943
|
+
context — still follows the theme, with no palette threaded to every element
|
|
944
|
+
that paints.
|
|
1024
945
|
|
|
1025
946
|
Tokens are not colour-only; `padding: '$gutter'` resolves a number just as
|
|
1026
947
|
well. And they resolve wherever a style value is written: in a state block, in
|
|
@@ -1055,7 +976,7 @@ which is the desktop's `sans-serif` unless the window itself was given a
|
|
|
1055
976
|
theme. An app that ships its own fonts then draws its headings in the face it
|
|
1056
977
|
shipped and its body copy in whatever the machine calls sans-serif, which is
|
|
1057
978
|
both wrong and slow: resolving a generic family costs an `fc-match` per weight
|
|
1058
|
-
on the first paint (~110ms each
|
|
979
|
+
on the first paint (~110ms each).
|
|
1059
980
|
|
|
1060
981
|
Three ways to say it. For a whole app the first says it once, for the widgets
|
|
1061
982
|
and every window; the last is the one for a subtree:
|
|
@@ -1072,9 +993,8 @@ and every window; the last is the one for a subtree:
|
|
|
1072
993
|
</box>
|
|
1073
994
|
```
|
|
1074
995
|
|
|
1075
|
-
`monoFamily` has no such fallback — nothing is monospace unless it says so,
|
|
1076
|
-
|
|
1077
|
-
an app, which are written by components that never meet, can be set from one
|
|
996
|
+
`monoFamily` has no such fallback — nothing is monospace unless it says so,
|
|
997
|
+
with `fontFamily: '$monoFamily'`, so an app's code surfaces are set from one
|
|
1078
998
|
place.
|
|
1079
999
|
|
|
1080
1000
|
A `theme` prop anywhere scopes its subtree, and an inner one merges over the
|
|
@@ -1091,32 +1011,24 @@ by `<ThemeProvider>`, which plants the resolved palette in the tree for this
|
|
|
1091
1011
|
lookup to find. Hand-writing `theme={{ accentHover: … }}` on a box gets the
|
|
1092
1012
|
merge and not the derivation.
|
|
1093
1013
|
|
|
1094
|
-
Changing the theme restyles the subtree in place
|
|
1095
|
-
not change
|
|
1096
|
-
memoised text layouts under it, or cached text would keep painting the old
|
|
1097
|
-
colour.
|
|
1014
|
+
Changing the theme restyles the subtree in place, nodes whose own props did
|
|
1015
|
+
not change included.
|
|
1098
1016
|
|
|
1099
1017
|
An unknown token is **reported and dropped**: the message names the token,
|
|
1100
1018
|
the element, the component that wrote it, and every token the palette in
|
|
1101
1019
|
force does have; that one property goes missing, so the widget paints
|
|
1102
1020
|
visibly wrong rather than the app dying; and `process.exitCode` is set to 1,
|
|
1103
|
-
so a test run, a CI job or a supervisor still counts the run as failed.
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
a build that would rather stop than paint something wrong. Strict mode also
|
|
1115
|
-
throws in the right _place_: the error is raised from the offending node's
|
|
1116
|
-
own `commitMount`, so an error boundary at any depth inside the window
|
|
1117
|
-
catches it and shows a fallback for that subtree. Where React genuinely is
|
|
1118
|
-
not on the stack — the desktop appearance switch — strict mode crashes,
|
|
1119
|
-
which is what it was asked for.
|
|
1021
|
+
so a test run, a CI job or a supervisor still counts the run as failed. It
|
|
1022
|
+
is not a throw because resolution also happens where no error boundary can
|
|
1023
|
+
catch one — above every boundary inside a `<window>`, or from the desktop's
|
|
1024
|
+
light/dark switch with no React on the stack.
|
|
1025
|
+
|
|
1026
|
+
Set **`REACT_X11_STRICT_TOKENS=1`** to make it fatal — for a kiosk, or
|
|
1027
|
+
a build that would rather stop than paint something wrong. Strict mode
|
|
1028
|
+
throws from the offending node's own `commitMount`, so an error boundary at
|
|
1029
|
+
any depth inside the window catches it and shows a fallback for that
|
|
1030
|
+
subtree. Where React genuinely is not on the stack — the desktop appearance
|
|
1031
|
+
switch — strict mode crashes, which is what it was asked for.
|
|
1120
1032
|
|
|
1121
1033
|
A token with **no** theme above it at all warns in dev — that one is
|
|
1122
1034
|
otherwise silent, since the whole style is stripped rather than one value
|
|
@@ -1127,17 +1039,15 @@ node that wears it, not just the first.
|
|
|
1127
1039
|
|
|
1128
1040
|
Resolution walks the node tree, not React context, so a palette has to reach
|
|
1129
1041
|
the tree to be seen. `<ThemeProvider>` puts it in both places — the context
|
|
1130
|
-
widgets read with `useTheme()`, and a `theme` prop on a node
|
|
1131
|
-
makes provider and token one mechanism rather than two
|
|
1042
|
+
widgets read with `useTheme()`, and a `theme` prop on a node
|
|
1132
1043
|
([components.md](components.md#theming)). Widgets plant their own merged
|
|
1133
1044
|
palette on their root node as well, so `$tokens` work inside a widget
|
|
1134
1045
|
subtree — and in a style you pass one — with no provider anywhere.
|
|
1135
1046
|
|
|
1136
1047
|
With no `theme` prop above it at all, a token resolves against **the
|
|
1137
1048
|
desktop's palette** — `backgroundColor: '$background'` in an app that never
|
|
1138
|
-
wrote a `<ThemeProvider>` is dark on a dark desktop
|
|
1139
|
-
|
|
1140
|
-
reported the same way, naming every token the one in force does have.
|
|
1049
|
+
wrote a `<ThemeProvider>` is dark on a dark desktop
|
|
1050
|
+
([appearance.md](appearance.md)).
|
|
1141
1051
|
|
|
1142
1052
|
## Sticky positioning
|
|
1143
1053
|
|
|
@@ -1187,11 +1097,9 @@ const s = createStyles({
|
|
|
1187
1097
|
the rows that scroll under a header do not paint over it, and it takes the
|
|
1188
1098
|
press where it covers them. `zIndex` still orders it first. Give it a
|
|
1189
1099
|
background, or the rows show through.
|
|
1190
|
-
- **It lands in the frame that scrolled
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
scroll blit: a header riding with its rows costs nothing, and a held one
|
|
1194
|
-
repaints its own rect.
|
|
1100
|
+
- **It lands in the frame that scrolled**, with no re-render, so a header
|
|
1101
|
+
cannot trail its content by a frame; a header riding with its rows costs
|
|
1102
|
+
nothing, and a held one repaints its own rect.
|
|
1195
1103
|
- **`onLayout` reports where layout put the box**, not where it is held,
|
|
1196
1104
|
the same way a scroll does not fire it. Where it is held is
|
|
1197
1105
|
`getClientRects()`.
|
|
@@ -1201,9 +1109,9 @@ Both axes at once, and one sticky box inside another, are what
|
|
|
1201
1109
|
row of room names held under the banner, a column of times held at the left,
|
|
1202
1110
|
and the corner where the two meet.
|
|
1203
1111
|
|
|
1204
|
-
Not
|
|
1205
|
-
reaching a row — brings it to the pane's edge, which can be under a
|
|
1206
|
-
header.
|
|
1112
|
+
Not supported: CSS's `scroll-padding`. Scrolling a node into view — keyboard
|
|
1113
|
+
focus reaching a row — brings it to the pane's edge, which can be under a
|
|
1114
|
+
held header.
|
|
1207
1115
|
|
|
1208
1116
|
Sticky is the built-in [custom position](#custom-positions): the same
|
|
1209
1117
|
placement, written against the same context a registered one is handed.
|
|
@@ -1234,7 +1142,9 @@ registerPosition('parallax', {
|
|
|
1234
1142
|
|
|
1235
1143
|
- **It is a `position` value**, by name or with its options, and switches
|
|
1236
1144
|
like one — from a container query block too. The box is laid out exactly
|
|
1237
|
-
where `relative` would put it, and moved; nothing around it reflows.
|
|
1145
|
+
where `relative` would put it, and moved; nothing around it reflows. A box
|
|
1146
|
+
has one `position`, so to combine a scheme with `absolute`, wrap it in a
|
|
1147
|
+
box.
|
|
1238
1148
|
- **The insets are the scheme's.** Yoga never sees them as offsets: sticky
|
|
1239
1149
|
reads them as thresholds, and a registered position reads them as whatever
|
|
1240
1150
|
it documents.
|
|
@@ -1301,19 +1211,24 @@ so a grid copied from a web codebase lays out here as it was written:
|
|
|
1301
1211
|
</box>
|
|
1302
1212
|
```
|
|
1303
1213
|
|
|
1304
|
-
| on the grid box |
|
|
1305
|
-
| ----------------------------------------- |
|
|
1306
|
-
| `gridTemplateColumns`, `gridTemplateRows` |
|
|
1307
|
-
| `gridTemplateAreas` |
|
|
1308
|
-
| `gridAutoColumns`, `gridAutoRows` |
|
|
1309
|
-
| `gridAutoFlow` | `
|
|
1310
|
-
| `gap`, `columnGap`, `rowGap` |
|
|
1311
|
-
| `justifyItems`, `alignItems` |
|
|
1312
|
-
| `justifyContent`, `alignContent` |
|
|
1313
|
-
| **on a child** |
|
|
1314
|
-
| `gridColumn`, `gridRow` | `
|
|
1315
|
-
| `gridArea` |
|
|
1316
|
-
| `justifySelf`, `alignSelf` |
|
|
1214
|
+
| on the grid box | |
|
|
1215
|
+
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
1216
|
+
| `gridTemplateColumns`, `gridTemplateRows` | a track list, or a number: that many equal tracks |
|
|
1217
|
+
| `gridTemplateAreas` | one quoted string per row — `'"head head" "side main"'` — or an array of the rows |
|
|
1218
|
+
| `gridAutoColumns`, `gridAutoRows` | the size of a track the grid adds for a child placed past its template; a number is a length |
|
|
1219
|
+
| `gridAutoFlow` | `'row'` (the default) or `'column'`, and `'dense'` to back-fill a hole |
|
|
1220
|
+
| `gap`, `columnGap`, `rowGap` | between the tracks |
|
|
1221
|
+
| `justifyItems`, `alignItems` | where each child sits in its area: `'stretch'` (the default), `'flex-start'`, `'center'`, `'flex-end'` |
|
|
1222
|
+
| `justifyContent`, `alignContent` | where the tracks sit when they leave room |
|
|
1223
|
+
| **on a child** | |
|
|
1224
|
+
| `gridColumn`, `gridRow` | `2`, `'span 2'`, `'2 / 4'`, `'2 / span 3'`, `'1 / -1'` |
|
|
1225
|
+
| `gridArea` | a name from `gridTemplateAreas`, or four lines; `gridColumn` and `gridRow` beside it win for their own axis |
|
|
1226
|
+
| `justifySelf`, `alignSelf` | its own place in its area, over the grid's |
|
|
1227
|
+
|
|
1228
|
+
Each property is CSS's of the same name (`gridTemplateColumns` is
|
|
1229
|
+
`grid-template-columns`). A track list takes lengths in `px` (logical
|
|
1230
|
+
pixels), `%` and `fr`; `auto`, `min-content`, `max-content`; `minmax()`,
|
|
1231
|
+
`fit-content()`; and `repeat()` with a count, `auto-fill` or `auto-fit`.
|
|
1317
1232
|
|
|
1318
1233
|
Things to know:
|
|
1319
1234
|
|
|
@@ -1338,9 +1253,7 @@ Things to know:
|
|
|
1338
1253
|
gap is as wide as it was written. Chrome lays a track out at a fraction of
|
|
1339
1254
|
a pixel, so a row whose content fits in the fraction can wrap one line
|
|
1340
1255
|
differently here.
|
|
1341
|
-
- **It
|
|
1342
|
-
part of the test suite (`test/grid-conformance.test.js`) and lays out here
|
|
1343
|
-
within a pixel of Chrome's, but for the fraction above.
|
|
1256
|
+
- **It matches Chrome** within a pixel, but for the fraction above.
|
|
1344
1257
|
- **Not here:** subgrid (a grid's child lays its own children out), baseline
|
|
1345
1258
|
alignment, named lines (`[full-start]` — name an area instead) and the
|
|
1346
1259
|
`grid` and `grid-template` shorthands. A value that uses one is an error
|
|
@@ -1358,6 +1271,8 @@ Things to know:
|
|
|
1358
1271
|
it needs anyway. What a child answered is remembered until something inside
|
|
1359
1272
|
it changes, so a frame in which one card's text changed lays out that card.
|
|
1360
1273
|
|
|
1274
|
+
Design notes: [design/architecture/grid-layout.md](../design/architecture/grid-layout.md).
|
|
1275
|
+
|
|
1361
1276
|
## Custom layouts
|
|
1362
1277
|
|
|
1363
1278
|
`layout` hands a box's children to a layout algorithm in place of flexbox.
|
|
@@ -1399,12 +1314,10 @@ the cells are one height too.
|
|
|
1399
1314
|
|
|
1400
1315
|
Things to know:
|
|
1401
1316
|
|
|
1402
|
-
- **It runs inside the layout pass
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
`onLayout` and setting state, which paints the old arrangement first
|
|
1407
|
-
([react-features.md](react-features.md#measuring-a-node)).
|
|
1317
|
+
- **It runs inside the layout pass**, in the same frame — on a window
|
|
1318
|
+
resize, a live resize, a scroll or a container query flip, with no React
|
|
1319
|
+
render. Measuring in `onLayout` and setting state instead paints the old
|
|
1320
|
+
arrangement first ([react-features.md](react-features.md#measuring-a-node)).
|
|
1408
1321
|
- **It is a style property**, so a container query block can switch it:
|
|
1409
1322
|
`'@container width < 500': { layout: { name: 'masonry', columns: 1 } }`.
|
|
1410
1323
|
- **Right to left is mirrored for you.** An algorithm places from the left,
|
|
@@ -1422,9 +1335,8 @@ Things to know:
|
|
|
1422
1335
|
the box is laid out as flexbox — in the same frame.
|
|
1423
1336
|
|
|
1424
1337
|
Writing one of your own is `registerLayout` from `react-x11/host`
|
|
1425
|
-
([extending.md](extending.md#a-layout-algorithm-of-your-own)).
|
|
1426
|
-
|
|
1427
|
-
[architecture/custom-layout.md](architecture/custom-layout.md).
|
|
1338
|
+
([extending.md](extending.md#a-layout-algorithm-of-your-own)). Design notes:
|
|
1339
|
+
[design/architecture/custom-layout.md](../design/architecture/custom-layout.md).
|
|
1428
1340
|
|
|
1429
1341
|
## Transitions
|
|
1430
1342
|
|
|
@@ -1441,17 +1353,16 @@ property; an object picks them individually.
|
|
|
1441
1353
|
/>
|
|
1442
1354
|
```
|
|
1443
1355
|
|
|
1444
|
-
Numbers
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
into.
|
|
1356
|
+
Numbers interpolate, and colours interpolate per channel, so any colour the
|
|
1357
|
+
paint accepts animates. Two percentages of the same unit interpolate as
|
|
1358
|
+
numbers — `'-40%'` to `'100%'` is a value travelling across whatever contains
|
|
1359
|
+
it, with nothing measuring the container. A value with no meaningful
|
|
1360
|
+
midpoint — an enum like `flexDirection`, `auto`, a percentage against a
|
|
1361
|
+
pixel value — snaps instead, and `zIndex` never transitions. A duration
|
|
1362
|
+
keyed by a [shorthand](#lengths-and-shorthands) is its longhands', the
|
|
1363
|
+
longhand's own entry winning: `{ border: 150 }` fades the colour
|
|
1364
|
+
`border: '1px solid …'` set, and `{ padding: 200 }` moves each side
|
|
1365
|
+
`padding: '8px 16px'` expanded into.
|
|
1455
1366
|
|
|
1456
1367
|
The easing is a fixed ease-out cubic. A transition starts from **what is on
|
|
1457
1368
|
screen**, not from the declared value, so interrupting one reverses from
|
|
@@ -1459,18 +1370,16 @@ where it got to rather than jumping to the end first. One to or from
|
|
|
1459
1370
|
[`currentColor`](#currentcolor) runs between the colours as they are at the
|
|
1460
1371
|
change.
|
|
1461
1372
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
one lands. Nothing polls, and there is no per-widget
|
|
1373
|
+
A transition runs on the window's own frame clock, which stops the frame the
|
|
1374
|
+
last one lands: nothing polls, and there is no per-widget
|
|
1465
1375
|
`requestAnimationFrame`.
|
|
1466
1376
|
|
|
1467
|
-
Transitions may animate layout properties, unlike state blocks
|
|
1468
|
-
an inconsistency: a pointer move must never reflow the tree, but an author
|
|
1377
|
+
Transitions may animate layout properties, unlike state blocks: an author
|
|
1469
1378
|
who writes `transition: { left: 200 }` has asked for animated layout and
|
|
1470
1379
|
pays a layout pass per frame for it, and a repaint of whatever that pass
|
|
1471
|
-
moves
|
|
1472
|
-
|
|
1473
|
-
|
|
1380
|
+
moves. `Switch` is the worked example — the thumb is absolutely positioned
|
|
1381
|
+
and slides on `start`, because `justifyContent` would flip between the ends
|
|
1382
|
+
with nothing in between.
|
|
1474
1383
|
|
|
1475
1384
|
### Loops
|
|
1476
1385
|
|
|
@@ -1544,54 +1453,36 @@ const TypingDot = ({ index }) => (
|
|
|
1544
1453
|
A changed `delay` is a different loop and starts from the top, as a changed
|
|
1545
1454
|
`duration` does.
|
|
1546
1455
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
repeat. A loop has no target — it has two ends and a period. They are
|
|
1550
|
-
different declarations because they are different things, and one of them
|
|
1551
|
-
would otherwise have to be spelled as the other with a flag.
|
|
1552
|
-
|
|
1553
|
-
The easing default differs from a transition's for the same reason. A change
|
|
1554
|
-
that ends looks right slowing into its new value; a cycle that restarts
|
|
1555
|
-
would stutter at the wrap, so a loop is `linear` unless you say otherwise.
|
|
1456
|
+
A loop's easing is `linear` unless you say otherwise, where a transition's is
|
|
1457
|
+
ease-out: a cycle that restarts would stutter at the wrap.
|
|
1556
1458
|
|
|
1557
|
-
**A loop costs a repaint of what moves
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
`setState`, is a re-render of the component and a repaint of whatever the
|
|
1562
|
-
damage heuristics decide, at a cadence unrelated to when the window can
|
|
1563
|
-
present.
|
|
1459
|
+
**A loop costs a repaint of what moves**, on the window's frame clock: an
|
|
1460
|
+
indeterminate progress bar repaints the bar, not the window. Prefer it to
|
|
1461
|
+
`setInterval` → `setState`, which re-renders the component on a cadence
|
|
1462
|
+
unrelated to when the window can present.
|
|
1564
1463
|
|
|
1565
1464
|
On macOS a loop — or a transition — on a plain box's colour, border width
|
|
1566
|
-
or radius
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
the
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
| | |
|
|
1585
|
-
| -------------------------------------------------------------------------------------------------------------------------- | --- |
|
|
1586
|
-
| the window is unmapped, minimized, or fully obscured under a bare WM | |
|
|
1587
|
-
| anything above the node hides it — `display: 'none'`, `<Suspense>`, `<Activity>` | |
|
|
1588
|
-
| the node unmounts, or the style stops declaring the loop | |
|
|
1589
|
-
| the desktop asked for **reduced motion** ([system.md](system.md#usedesktopsettings--how-the-desktop-wants-an-app-to-feel)) | |
|
|
1590
|
-
| all the loop can draw is out of sight — scrolled out of its pane, clipped away by a box above it, off the window | |
|
|
1465
|
+
or radius runs in Core Animation and costs no frames at all. So does a loop
|
|
1466
|
+
on an inset of a box that is `position: 'absolute'` and
|
|
1467
|
+
`pointerEvents: 'none'`, as the block of `<ProgressBar indeterminate>` is;
|
|
1468
|
+
without `pointerEvents: 'none'` it stays on the frame clock, because input is
|
|
1469
|
+
hit where the layout put the box. Anything else runs as above.
|
|
1470
|
+
`examples/animation.jsx` has both shapes side by side, with a frame counter
|
|
1471
|
+
in the terminal that shows the difference.
|
|
1472
|
+
|
|
1473
|
+
**And it stops itself** whenever it goes off the screen:
|
|
1474
|
+
|
|
1475
|
+
- the window is unmapped, minimized, or fully obscured under a bare WM;
|
|
1476
|
+
- anything above the node hides it — `display: 'none'`, `<Suspense>`,
|
|
1477
|
+
`<Activity>`;
|
|
1478
|
+
- the node unmounts, or the style stops declaring the loop;
|
|
1479
|
+
- the desktop asked for **reduced motion**
|
|
1480
|
+
([system.md](system.md#usedesktopsettings--how-the-desktop-wants-an-app-to-feel));
|
|
1481
|
+
- all the loop can draw is out of sight — scrolled out of its pane, clipped
|
|
1482
|
+
away by a box above it, off the window.
|
|
1591
1483
|
|
|
1592
1484
|
Each one leaves the frame clock idle, and every one of them runs the loop
|
|
1593
|
-
again when it goes away — with its phase reset
|
|
1594
|
-
mid-cycle would have been drawing where nobody could see it.
|
|
1485
|
+
again when it goes away — with its phase reset.
|
|
1595
1486
|
|
|
1596
1487
|
Out of sight is judged after each layout pass, by what **every** frame of
|
|
1597
1488
|
the loop can reach and never by where the current one has the node: the
|
|
@@ -1613,8 +1504,7 @@ still shows something in progress.
|
|
|
1613
1504
|
|
|
1614
1505
|
Nothing about this is a per-frame callback: `animation` describes the motion
|
|
1615
1506
|
and the renderer runs it. For a `<glarea>` drawing its own frames,
|
|
1616
|
-
[`frameLoop`](gl.md) is the per-frame seam
|
|
1617
|
-
built yet.
|
|
1507
|
+
[`frameLoop`](gl.md) is the per-frame seam; there is no 2D one.
|
|
1618
1508
|
|
|
1619
1509
|
## Window size queries
|
|
1620
1510
|
|
|
@@ -1635,12 +1525,9 @@ const s = createStyles({
|
|
|
1635
1525
|
merged in declaration order, before state blocks — so a `:hover` inside the
|
|
1636
1526
|
wide layout still wins over the wide layout.
|
|
1637
1527
|
|
|
1638
|
-
**A size query may set layout properties, unlike a state block.**
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
already required, so it costs nothing extra. Nodes that declare one are
|
|
1642
|
-
registered with their window and re-resolved just before it lays out, and
|
|
1643
|
-
only when the size actually changed.
|
|
1528
|
+
**A size query may set layout properties, unlike a state block.** It is only
|
|
1529
|
+
re-evaluated inside the layout pass a resize already requires, and only when
|
|
1530
|
+
the size actually changed, so it costs nothing extra.
|
|
1644
1531
|
|
|
1645
1532
|
A malformed query is an error rather than a key that silently never
|
|
1646
1533
|
matches.
|
|
@@ -1672,11 +1559,14 @@ A container is declared with the **`container` style property** — CSS's
|
|
|
1672
1559
|
**nearest ancestor that declares one**. Wrapping the card in another `<box>`
|
|
1673
1560
|
for a border changes nothing: the parent is not the container, the declared
|
|
1674
1561
|
ancestor is, which is what lets a component be put anywhere and still get
|
|
1675
|
-
the answer its author meant.
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1562
|
+
the answer its author meant. A scroll pane is not a container unless it
|
|
1563
|
+
declares one, though it is the ideal one to declare. A node's blocks never
|
|
1564
|
+
answer to its own size, even when it declares itself a container: its
|
|
1565
|
+
declaration is for the blocks under it. The declaration changes nothing
|
|
1566
|
+
about the container itself; it is neither layout nor paint. Being a style
|
|
1567
|
+
property, it can be set from inside a query block — a box that is a
|
|
1568
|
+
container only in the wide layout — and `false` takes a declaration back
|
|
1569
|
+
from earlier in a style array.
|
|
1680
1570
|
|
|
1681
1571
|
`width` and `height`, with `>=`, `<=`, `>` or `<`, in the logical pixels the
|
|
1682
1572
|
dependent's own lengths are written in. Blocks that match are merged in
|
|
@@ -1734,16 +1624,12 @@ would; that is the reason to write a style as a block where one can.
|
|
|
1734
1624
|
|
|
1735
1625
|
### A block must not move the size it asks about
|
|
1736
1626
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
notices (the node comes back to an answer it already held this frame),
|
|
1744
|
-
**holds the answer it has**, and says so once in development, naming the
|
|
1745
|
-
node. The answer stays held until the container's size moves for some other
|
|
1746
|
-
reason.
|
|
1627
|
+
Yoga has no size containment, so here this is a rule, detected rather than
|
|
1628
|
+
prevented. A block that grows the container past the very threshold that
|
|
1629
|
+
turned it on — and shrinks it back when it turns off — has no size that
|
|
1630
|
+
satisfies it. The renderer notices, **holds the answer it has**, and says so
|
|
1631
|
+
once in development, naming the node. The answer stays held until the
|
|
1632
|
+
container's size moves for some other reason.
|
|
1747
1633
|
|
|
1748
1634
|
The way to stay clear of it is also what makes a good container: a size that
|
|
1749
1635
|
does not come from the content below it.
|
|
@@ -1758,6 +1644,8 @@ The common responsive pattern — less content when narrow, more when wide —
|
|
|
1758
1644
|
is monotone and settles on the first re-layout; a warning means the design
|
|
1759
1645
|
genuinely contradicts itself.
|
|
1760
1646
|
|
|
1647
|
+
Design notes: [design/architecture/container-queries.md](../design/architecture/container-queries.md).
|
|
1648
|
+
|
|
1761
1649
|
## Capability queries
|
|
1762
1650
|
|
|
1763
1651
|
`'@supports transparency'` is to `@supports` what the size queries are to
|
|
@@ -1781,14 +1669,14 @@ const s = createStyles({
|
|
|
1781
1669
|
|
|
1782
1670
|
Write the design that works everywhere as the base and the enhancement in
|
|
1783
1671
|
the block — the same way you would write a `@media` query for a wider
|
|
1784
|
-
screen.
|
|
1785
|
-
|
|
1672
|
+
screen. Get it the other way round and the fallback is the _unusual_ case,
|
|
1673
|
+
which is the one nobody tests.
|
|
1786
1674
|
|
|
1787
1675
|
`transparency` is true only when **both** halves hold: the window was
|
|
1788
1676
|
created on a 32-bit visual (`<window transparent>`, and the display had one
|
|
1789
1677
|
to give), **and** a compositor is running to blend it. Either half missing
|
|
1790
1678
|
and a transparent corner is a _black_ corner — X shows the raw pixels — so
|
|
1791
|
-
the
|
|
1679
|
+
the answer is no, and the block does not apply.
|
|
1792
1680
|
|
|
1793
1681
|
It is answered **per window**, not per display. A component rendered inside
|
|
1794
1682
|
a `<popup transparent>` gets the translucent design; the same component
|
|
@@ -1809,107 +1697,10 @@ which answers the same question about the display.
|
|
|
1809
1697
|
An unknown feature name is an error, like a malformed size query.
|
|
1810
1698
|
|
|
1811
1699
|
Running with `REACT_X11_NO_TRANSPARENCY=1` makes the answer false on any
|
|
1812
|
-
display, so the base design
|
|
1813
|
-
|
|
1700
|
+
display, so the base design can be looked at without stopping the compositor
|
|
1701
|
+
for the whole session. See
|
|
1814
1702
|
[debugging.md](debugging.md#react_x11_no_transparency1).
|
|
1815
1703
|
|
|
1816
|
-
## Decided
|
|
1817
|
-
|
|
1818
|
-
- **`':hover'`, not `_hover`.** The CSS spelling costs a pair of quotes and
|
|
1819
|
-
buys transfer from every other styling system.
|
|
1820
|
-
- **Declared containers, not the parent.** `'@container'` answers to the
|
|
1821
|
-
nearest ancestor that _declares_ itself one, never to whatever happens to
|
|
1822
|
-
be the parent: a wrapper `<box>` added for a border must not change a
|
|
1823
|
-
component's answer. CSS spent a decade on element queries before shipping
|
|
1824
|
-
container queries with exactly this requirement. Nor is a scroller a
|
|
1825
|
-
container without saying so — it is the ideal one to declare, but the
|
|
1826
|
-
presence of one between a node and its container must not silently change
|
|
1827
|
-
which box answers. And there is no self query: a node's own size deciding
|
|
1828
|
-
its own layout is the cycle containment exists to forbid.
|
|
1829
|
-
- **Inherited properties, no relational selectors.** `color` and the font
|
|
1830
|
-
properties travel down the tree; nothing matches a rule against it. So
|
|
1831
|
-
there is no `:hover > child`, no sibling combinator and no Tailwind-style
|
|
1832
|
-
`group`. Each of those exists to move a value across the tree, and
|
|
1833
|
-
inheritance already does it in the one direction that is cheap: a parent's
|
|
1834
|
-
`:hover` reaches its children because `color` is inherited, and a child's
|
|
1835
|
-
hover reaches its parents because the hover path is the ancestor chain.
|
|
1836
|
-
What is left over — a **sibling** reacting to a sibling — stays in React,
|
|
1837
|
-
where `useControl` already returns `hover`/`focused`/`pressed` as state
|
|
1838
|
-
([components.md](components.md#basic-controls)). `<Checkbox>` does exactly
|
|
1839
|
-
that, because its well is a sibling of the label the press lands on.
|
|
1840
|
-
- **`currentColor` resolves where it is painted, not in the style.**
|
|
1841
|
-
Resolving it where a `$token` resolves, once per style swap, would have
|
|
1842
|
-
made every reader of a colour simpler and every inherited change wrong: a
|
|
1843
|
-
`:hover { color }` on a row restyles no node under it, so an ink baked
|
|
1844
|
-
into their styles would be stale in every one. So the style keeps the
|
|
1845
|
-
keyword, as CSS's computed value does, and every reader — the paint on
|
|
1846
|
-
each backend, a Cocoa layer's properties, the blits deciding what a box
|
|
1847
|
-
covers — asks `resolveColor`.
|
|
1848
|
-
- **Sticky sticks to a pane that scrolls, not to anything that clips.** CSS
|
|
1849
|
-
makes every `overflow` but `visible` a scroll container, so a header
|
|
1850
|
-
inside a card with `overflow: hidden` sticks to the card — which never
|
|
1851
|
-
scrolls — and appears to do nothing, the most asked-about sticky bug there
|
|
1852
|
-
is. Here `'hidden'` is CSS's `clip`: it clips and scrolls nothing, and only
|
|
1853
|
-
`overflow: 'scroll'` makes a pane (see
|
|
1854
|
-
[Sticky positioning](#sticky-positioning)). A header inside a rounded card
|
|
1855
|
-
sticks to the list the card scrolls in, and stays inside the card.
|
|
1856
|
-
- **A registered position is a `position` value, not a property of its
|
|
1857
|
-
own.** CSS's `position` already picks a positioning scheme, and sticky is
|
|
1858
|
-
one whose insets mean something other than an offset; a scheme of your own
|
|
1859
|
-
is the same kind of thing. The first spelling, `placement`, was already
|
|
1860
|
-
the anchor vocabulary — which side of its trigger a popup opens on — and
|
|
1861
|
-
threw on the first `<popup placement>` it met. What one property costs is
|
|
1862
|
-
composing a scheme with `absolute`, which a wrapping box answers.
|
|
1863
|
-
- **A layout is a style property, and runs inside the pass.** What a layout
|
|
1864
|
-
decides depends on sizes the pass produces, so a React-side one —
|
|
1865
|
-
measure in `onLayout`, set state — paints the old arrangement first, and
|
|
1866
|
-
misses every pass React never sees: a scroll, a live resize, a
|
|
1867
|
-
transition. A style property is also one a container query block can
|
|
1868
|
-
switch in the same frame.
|
|
1869
|
-
- **The renderer mirrors right to left, not the algorithm.** An algorithm
|
|
1870
|
-
places from the left, once, and each slot is mirrored across the box when
|
|
1871
|
-
it reads right to left — the way yoga mirrors a flex row, so no layout can
|
|
1872
|
-
forget to.
|
|
1873
|
-
- **`null` is unset, and a layout value the layout cannot take is an error
|
|
1874
|
-
where it is written.** `width: wide ? 240 : null` leaves the width to the
|
|
1875
|
-
layout, as `undefined` does — yoga's own setters threw on `null`.
|
|
1876
|
-
`width: 'hidden'`, `padding: 'auto'` or `flexDirection: 'sideways'` is an
|
|
1877
|
-
error at the commit in development, naming the property, and that includes
|
|
1878
|
-
a query block that does not match yet: the block is applied when it starts
|
|
1879
|
-
matching, which is inside a frame, and a throw from there used to take the
|
|
1880
|
-
process down. A value that only turns out wrong in a frame — a `$token`
|
|
1881
|
-
resolving to one — is dropped there and reported, the way CSS drops a
|
|
1882
|
-
declaration it cannot parse, and the rest of the block still applies.
|
|
1883
|
-
- **A colour that does not parse is dropped, not painted.**
|
|
1884
|
-
`backgroundColor: 'nonsense'`, `7` or `'#12'` is an error at the commit in
|
|
1885
|
-
development, naming the property — in the style, in a state block, as a
|
|
1886
|
-
gradient stop or as a shadow's colour. `''`, like `undefined`, means
|
|
1887
|
-
unset. Where one gets past that — a `$token` whose palette entry is not a
|
|
1888
|
-
colour, or any value in a production build — the paint drops it the way
|
|
1889
|
-
CSS drops a declaration it cannot parse: no fill and no stroke, no
|
|
1890
|
-
gradient or shadow at all rather than half of one, and the inherited ink
|
|
1891
|
-
for text. On X11 it used to throw from the frame, where no error boundary
|
|
1892
|
-
could catch it, and the other backends painted it black.
|
|
1893
|
-
|
|
1894
|
-
- **The more specific property wins, not the later one.** CSS resolves a
|
|
1895
|
-
longhand against its shorthand by order. A flattened style has no order
|
|
1896
|
-
left — `[base, { padding: '8px 16px' }]` and `{ paddingTop: 0, … }` are
|
|
1897
|
-
keys of one object by the time anything reads them — so a longhand beats
|
|
1898
|
-
its shorthand and a side beats all four, wherever each is written. It is
|
|
1899
|
-
the rule `paddingLeft` beside `padding: 8` always followed.
|
|
1900
|
-
- **A length is pixels, and a unit nothing reads is an error.** `'12px'` is
|
|
1901
|
-
12 — scaled for the display like any number, where it used to reach yoga
|
|
1902
|
-
as a string the scale skipped and the border setter read as NaN — and
|
|
1903
|
-
`'2ex'` names its unit rather than becoming 2.
|
|
1904
|
-
- **`rem` is the palette's `fontSize`, not 16.** CSS's root font size is
|
|
1905
|
-
whatever the document's root says, and here that is the theme: the size
|
|
1906
|
-
every label nobody styled is set in. So spacing written in `rem` follows a
|
|
1907
|
-
theme that sets a bigger type size, as the palette's own radii and
|
|
1908
|
-
paddings already do, and an app porting web numbers that assume 16 sets
|
|
1909
|
-
`fontSize: 16` on its theme. The nearest palette is the one read, the way
|
|
1910
|
-
a `$token` is — a `<ThemeProvider value={{ fontSize: 12 }}>` around a
|
|
1911
|
-
dense panel re-bases it.
|
|
1912
|
-
|
|
1913
1704
|
## Elements that are not styled
|
|
1914
1705
|
|
|
1915
1706
|
The declarative SVG children carry a vocabulary of their own — `<circle cx
|
|
@@ -1917,12 +1708,3 @@ r fill>` is SVG's, not the style channel's — so the style channel does not
|
|
|
1917
1708
|
apply to them, the same way it does not apply to an `<input type>` in the
|
|
1918
1709
|
DOM. They report
|
|
1919
1710
|
`stylable === false` and their props are passed through untouched.
|
|
1920
|
-
|
|
1921
|
-
## Next
|
|
1922
|
-
|
|
1923
|
-
A
|
|
1924
|
-
`boxShadow` that a `<popup>` can cast (the window needs a translucent margin
|
|
1925
|
-
around itself first — see [elements.md](elements.md) on `transparent`
|
|
1926
|
-
popups). Container queries shipped; the design record, with the targeting
|
|
1927
|
-
forms considered and the cost measured, is
|
|
1928
|
-
[architecture/container-queries.md](architecture/container-queries.md).
|