@waveso/docs 0.8.0 → 0.9.1
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/CHANGELOG.md +181 -0
- package/README.md +11 -4
- package/dist/react/search-dialog.d.ts +24 -1
- package/dist/react/search-dialog.js +104 -16
- package/dist/react/sidebar.js +23 -2
- package/dist/styles.css +263 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,186 @@
|
|
|
1
1
|
# @waveso/docs
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 80d3db4: The sidebar's handle and the search shortcut stop competing with the page.
|
|
8
|
+
|
|
9
|
+
**The handle is 16 × 56 and faded.** It was 20 × 80 at full strength — a solid
|
|
10
|
+
slab beside the reading column, for a control nobody looks at while reading. It
|
|
11
|
+
now sits at 40% until a pointer or a caret reaches it, the same treatment as the
|
|
12
|
+
tree's markers.
|
|
13
|
+
|
|
14
|
+
⚠️ BOTH MARKS FADE, NOT THE PILL ALONE. Fading `::before` by itself leaves crisp
|
|
15
|
+
dots on a washed-out slab, which reads louder than the solid grip it replaced.
|
|
16
|
+
|
|
17
|
+
⚠️ AND NOT ON THE `<button>`, which would take the focus ring down with it —
|
|
18
|
+
`opacity` applies to the whole element, outline included, so a keyboard reader
|
|
19
|
+
would get a 40% indicator on the control they had just moved to. It is on the
|
|
20
|
+
two pseudo-elements, and `:focus-visible` restores both.
|
|
21
|
+
|
|
22
|
+
⚠️ THE TAP TARGET IS UNCHANGED BY ANY OF IT. The button is the whole strip and
|
|
23
|
+
runs the height of the column; the pill is paint. At 16px plus 4px of padding a
|
|
24
|
+
side the strip is 24px wide — WCAG 2.5.8's minimum to the pixel, and a test now
|
|
25
|
+
says so, because the next narrowing is the one that fails it.
|
|
26
|
+
|
|
27
|
+
⚠️ THE DOTS DO NOT SCALE WITH THE PILL. They are `box-shadow` offsets on one
|
|
28
|
+
element, so they span 18px whatever the pill does. Found by shortening it to
|
|
29
|
+
16px tall and watching the outer two render outside it.
|
|
30
|
+
|
|
31
|
+
**`⌘K` is levelled with `Search`.** Equal `font-size` in two families is not
|
|
32
|
+
equal type: the label is `ui-sans-serif` and the badge `ui-monospace`, which
|
|
33
|
+
draws 0.7292px of cap per px. `1.012em` is what puts the `K`'s cap on the `S`'s.
|
|
34
|
+
|
|
35
|
+
⚠️ AND THE SYMBOL IS `1.369em`, THE MEASURED INK RATIO — NOT A HAIR MORE. At
|
|
36
|
+
`1.45em` the `⌘` stood 6% above the cap line, and the badge read as _bigger
|
|
37
|
+
type_ than the label: 88 device px of ink against the word's 85, its top three
|
|
38
|
+
higher, its centre 1.5 out. Same letter height, louder cluster. At the ratio,
|
|
39
|
+
`Search` and `⌘K` measure 85 and 85 and share a centre to the device pixel.
|
|
40
|
+
|
|
41
|
+
⚠️ AND `line-height: 0` ON THE SYMBOL, WHICH IS WHAT MADE THE TWO CENTRE. A line
|
|
42
|
+
box is as tall as the tallest inline box in it, so the 20.5px glyph made the
|
|
43
|
+
`<kbd>` 20.55px against the label's 17 — and flex centres them by their _boxes_,
|
|
44
|
+
so the `K` rode 1.9px high inside a box the symbol had stretched.
|
|
45
|
+
|
|
46
|
+
Also fixed: the focus-indicator test looked its selectors up with `indexOf`, so
|
|
47
|
+
`…:focus-visible` matched inside `…:focus-visible::before` — a different rule,
|
|
48
|
+
about pseudo-elements, with no business declaring an outline. It reported the
|
|
49
|
+
trigger as having no focus indicator while the trigger's own rule sat further
|
|
50
|
+
down the file declaring one.
|
|
51
|
+
|
|
52
|
+
- 27f63ec: Sidebar separators keep the case their author wrote them in.
|
|
53
|
+
|
|
54
|
+
⚠️ `text-transform: uppercase` ON A STRING THIS PACKAGE DOES NOT OWN. A
|
|
55
|
+
separator's text comes from a consumer's `meta.json` — `"---Reference---"` —
|
|
56
|
+
and restyling it is this package rewriting words in a language it cannot read.
|
|
57
|
+
Portuguese `Referência` shipped as `REFERÊNCIA`. Turkish trades its dotted and
|
|
58
|
+
dotless `i` for each other under a naive uppercase. No CJK script has a case to
|
|
59
|
+
transform at all, so those authors got the `letter-spacing` and none of the
|
|
60
|
+
effect it existed to rescue.
|
|
61
|
+
|
|
62
|
+
The string was already a prop. Its shape was not, and there was no way to turn
|
|
63
|
+
this off short of overriding the rule.
|
|
64
|
+
|
|
65
|
+
Reading as a divider rather than as another row is done by size, weight and a
|
|
66
|
+
subtle colour — none of which touch a character. `letter-spacing` goes with the
|
|
67
|
+
caps: it is there to make uppercase legible, and on sentence case it only reads
|
|
68
|
+
as loose.
|
|
69
|
+
|
|
70
|
+
## 0.9.0
|
|
71
|
+
|
|
72
|
+
### Minor Changes
|
|
73
|
+
|
|
74
|
+
- 216f2df: **The search dialog says how to drive it.** A magnifier on the trigger and in
|
|
75
|
+
the input, and a footer carrying `↑` `↓` Select · `↵` Open · `Esc` Close.
|
|
76
|
+
|
|
77
|
+
⚠️ AND IT DELETES A BUTTON THAT SAID `Close` IN HARDCODED ENGLISH. Every other
|
|
78
|
+
user-facing string in this package had been lifted to a prop; that one was
|
|
79
|
+
missed, in the one dialog a reader cannot leave without it — so a Portuguese
|
|
80
|
+
site rendered a Portuguese dialog with an English way out. It is `closeLabel`
|
|
81
|
+
now, alongside `selectLabel` and `openLabel`.
|
|
82
|
+
|
|
83
|
+
The key-caps beside them are glyphs and stay untranslated: an arrow is an
|
|
84
|
+
arrow, and `Esc` is `Esc` on a Portuguese keyboard. The props are the verbs,
|
|
85
|
+
which are not.
|
|
86
|
+
|
|
87
|
+
⚠️ THE DISMISS CONTROL IS A BUTTON, NOT A THIRD HINT. Under `(hover: none) and
|
|
88
|
+
(pointer: coarse)` the two hints are hidden — on the same reasoning as the
|
|
89
|
+
trigger's `⌘K`, that an instruction to press a key is one a reader on a phone
|
|
90
|
+
cannot follow — and that leaves this as the only pointer route out of the
|
|
91
|
+
dialog. It is not hidden with them.
|
|
92
|
+
|
|
93
|
+
⚠️ IT ALSO MOVED PAST THE RESULTS. The button it replaces sat in the input row,
|
|
94
|
+
so one Tab from the query landed on _Close_ rather than on the first result —
|
|
95
|
+
past every answer the reader had just asked for. Document order is tab order
|
|
96
|
+
here, and a test now holds it.
|
|
97
|
+
|
|
98
|
+
The hints are `aria-hidden`: they describe the pointer-free path through a
|
|
99
|
+
listbox that a screen reader already exposes through `role`,
|
|
100
|
+
`aria-activedescendant` and `aria-posinset`, so announcing them adds two lines
|
|
101
|
+
of symbols and no information. The magnifiers are hidden for the reason the
|
|
102
|
+
trigger's own label is pinned — named from content, that button once announced
|
|
103
|
+
as "Search Ctrl K".
|
|
104
|
+
|
|
105
|
+
Published sizes rise with it: the quick start's total 14 → 14.3 KB, the search
|
|
106
|
+
dialog 9.3 → 9.5 KB. Budgets raised in `size-budget.json` with the reason.
|
|
107
|
+
|
|
108
|
+
⚠️ AND THE TRIGGER NOW SHARES A COLUMN WITH THE TREE. It sits directly above
|
|
109
|
+
the navigation, so its magnifier is the first thing in the same column as every
|
|
110
|
+
folder and page marker below it and its label starts the same column as every
|
|
111
|
+
title — and both were out, measured at 1280px by 3px and 11px. The trigger was
|
|
112
|
+
spaced as a standalone control: 10px of inline padding against the rows' 8px,
|
|
113
|
+
and a 16px gap against their 8px. It carries a 1px border the rows do not, so
|
|
114
|
+
the fix is `calc(0.5rem - 1px)` rather than `0.5rem` — matching the number
|
|
115
|
+
instead of the content edge leaves it 1px out and looks fixed in a screenshot.
|
|
116
|
+
A browser test measures both columns against the tree's.
|
|
117
|
+
|
|
118
|
+
## The trigger's shortcut is plain text again
|
|
119
|
+
|
|
120
|
+
`⌘K` was a bordered chip inside a bordered, filled control — a chip on a chip,
|
|
121
|
+
sharing its fill, for a hint nobody clicks. It kept that border only because
|
|
122
|
+
the footer's key-caps were added to the same rule; the two are separate now,
|
|
123
|
+
and a test holds them apart. There was never a `background` on either: the
|
|
124
|
+
trigger's own `--wave-docs-bg-subtle` showed through, which is what made the
|
|
125
|
+
border read as a filled shape.
|
|
126
|
+
|
|
127
|
+
⚠️ AND THE `⌘` IS ITS OWN ELEMENT, BECAUSE CSS CANNOT SELECT A CHARACTER.
|
|
128
|
+
Measured in the shipped mono stack at 12px, the glyph carries 6.39px of ink
|
|
129
|
+
against the `K`'s 8.75px — a third short of the letter beside it, in one string
|
|
130
|
+
at one size. `1.45em` on the symbol brings it to 9.26px — a hair
|
|
131
|
+
taller than the letter, which is what makes the two read as one mark. `Ctrl` is
|
|
132
|
+
gated out of that rule by an attribute: it is a word set in the same face as
|
|
133
|
+
the `K`, and scaling it makes the hint shout.
|
|
134
|
+
|
|
135
|
+
⚠️ AND SIZE ALONE LEAVES IT FLOATING. `⌘` is drawn around the font's
|
|
136
|
+
mathematical axis rather than standing on the baseline like a capital, so at
|
|
137
|
+
that size its ink centre sits 2.24px above the `K`'s while inline layout aligns
|
|
138
|
+
the two by baseline. `vertical-align: -0.13em` drops it. Measured off a render
|
|
139
|
+
at 8x: both ink boxes centre on the same pixel, with the symbol 5% the taller.
|
|
140
|
+
|
|
141
|
+
New public class names: `.wave-docs-search-glyph`, `.wave-docs-search-footer`,
|
|
142
|
+
`.wave-docs-search-hint`, `.wave-docs-search-kbd`, `.wave-docs-search-trigger-mod`.
|
|
143
|
+
`.wave-docs-search-close` survives, restyled — it is a footer control now, not
|
|
144
|
+
a bordered button in the input row.
|
|
145
|
+
|
|
146
|
+
### Patch Changes
|
|
147
|
+
|
|
148
|
+
- 5647418: Sidebar groups stop collapsing behind the reader.
|
|
149
|
+
|
|
150
|
+
Expand three sections, click a page, and two of them shut. Or: open a section,
|
|
151
|
+
read a page in it, open another section, read a page in _that_ — and the first
|
|
152
|
+
one closes. Both reported from real use, and both the same defect.
|
|
153
|
+
|
|
154
|
+
⚠️ THE CAUSE WAS `setToggled({})` ON EVERY NAVIGATION. The reader's own state
|
|
155
|
+
and the route's default share one map — `toggled[key] ?? hasActive` — so
|
|
156
|
+
clearing it does not "reset the tree to its default" in any useful sense. The
|
|
157
|
+
default is _open only what holds the current page_, so wiping the map collapses
|
|
158
|
+
everything the reader had deliberately opened.
|
|
159
|
+
|
|
160
|
+
Navigation now opens whatever holds the page just arrived at, and closes
|
|
161
|
+
nothing. A group closes when the reader closes it.
|
|
162
|
+
|
|
163
|
+
⚠️ AND IT RECORDS `true` RATHER THAN DELETING THE KEY, WHICH IS THE HALF THAT
|
|
164
|
+
IS EASY TO GET WRONG. Deleting also reopens the group — it falls back to
|
|
165
|
+
`hasActive` — and looks correct for exactly one navigation. Read a page in one
|
|
166
|
+
section, then a page in another, and the first section has no entry left and no
|
|
167
|
+
longer holds the route, so it shuts. That is the second report, reproduced by
|
|
168
|
+
the obvious fix.
|
|
169
|
+
|
|
170
|
+
The state is also seeded from the first route rather than starting empty, so a
|
|
171
|
+
group open at first paint is open by _record_ rather than by inference. Without
|
|
172
|
+
it, landing on a deep page from a search result and clicking away collapses the
|
|
173
|
+
section you arrived in, while one you had opened by hand would have stayed.
|
|
174
|
+
|
|
175
|
+
Kept from the old reset: a group collapsed an hour ago must not hide the page
|
|
176
|
+
just navigated to. It is reopened explicitly.
|
|
177
|
+
|
|
178
|
+
It costs 60 gzipped bytes on the sidebar bundle — a walk that names the groups
|
|
179
|
+
holding the current route. The line it replaces cost nothing, which is the
|
|
180
|
+
point: the cheapest possible reset was also the one that threw away everything
|
|
181
|
+
the reader had opened. Published sizes rise with it: the quick start's total
|
|
182
|
+
14.3 → 14.5 KB, the navigation 3 → 3.1 KB.
|
|
183
|
+
|
|
3
184
|
## 0.8.0
|
|
4
185
|
|
|
5
186
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -69,16 +69,16 @@ Every figure below is a **ceiling**, and `pnpm size` fails the build if the meas
|
|
|
69
69
|
|
|
70
70
|
| | At most |
|
|
71
71
|
| --- | --- |
|
|
72
|
-
| Everything the quick start ships, gzipped | 14 KB |
|
|
73
|
-
| Search dialog and router wiring | 9.
|
|
74
|
-
| Navigation: one sidebar, open and closed | 3 KB |
|
|
72
|
+
| Everything the quick start ships, gzipped | 14.5 KB |
|
|
73
|
+
| Search dialog and router wiring | 9.5 KB |
|
|
74
|
+
| Navigation: one sidebar, open and closed | 3.1 KB |
|
|
75
75
|
| Table of contents | 0.9 KB |
|
|
76
76
|
| Copy-button runtime | 1.1 KB |
|
|
77
77
|
| hast over the wire vs HTML, prose page | 1.20× |
|
|
78
78
|
| hast over the wire vs HTML, code and tables | 1.12× |
|
|
79
79
|
| Highlighting vs no highlighting | 2.00× |
|
|
80
80
|
|
|
81
|
-
The first row is the honest total: a reader who lands on a page of your documentation downloads under 14 KB gzipped of JavaScript from this package, and that is the whole of it. No markdown parser and no syntax highlighter reach the browser at all — those run in Node at build time. Drop the search dialog and it is under 4 KB.
|
|
81
|
+
The first row is the honest total: a reader who lands on a page of your documentation downloads under 14.5 KB gzipped of JavaScript from this package, and that is the whole of it. No markdown parser and no syntax highlighter reach the browser at all — those run in Node at build time. Drop the search dialog and it is under 4 KB.
|
|
82
82
|
|
|
83
83
|
The one real cost is the middle pair: shipping a tree instead of a string is about 20% more brotli on a prose page, and about 12% on a page with code and tables, where Shiki's token spans dominate both representations equally. That is the price of never handing markup to `dangerouslySetInnerHTML`, and it is the first number a skeptical reviewer should ask for.
|
|
84
84
|
|
|
@@ -755,10 +755,17 @@ If your site sets Next's own `basePath` config, prefix `indexUrl` yourself: Next
|
|
|
755
755
|
| `loadingLabel` | `string` | `'Loading the search index…'` | While the index is fetched |
|
|
756
756
|
| `errorLabel` | `string` | `'Search is unavailable right now. Try reloading the page.'` | When it cannot be |
|
|
757
757
|
| `emptyLabel` | `string` | `'No results for “{query}”.'` | No matches. `{query}` is what was typed |
|
|
758
|
+
| `selectLabel` | `string` | `'Select'` | Footer hint beside `↑` `↓` |
|
|
759
|
+
| `openLabel` | `string` | `'Open'` | Footer hint beside `↵` |
|
|
760
|
+
| `closeLabel` | `string` | `'Close'` | The footer's dismiss button, beside `Esc` |
|
|
758
761
|
| `resultCountLabels` | `Partial<Record<Intl.LDMLPluralRule, string>>` | `{ one: '{count} result', other: '{count} results' }` | The live region, by plural category |
|
|
759
762
|
| `locale` | `string` | `<html lang>`, then `'en'` | Language tag for those plural rules |
|
|
760
763
|
| `miniSearchOptions` | `Partial<Options<SearchRecord>>` | — | See [Tuning](#tuning) |
|
|
761
764
|
|
|
765
|
+
The dialog's footer carries the three keyboard hints and the dismiss control. The key-caps beside them — `↑` `↓` `↵` `Esc` — are glyphs and are not translatable; the three props above are the verbs, which are.
|
|
766
|
+
|
|
767
|
+
Under `(hover: none) and (pointer: coarse)` the two hints are hidden, on the same reasoning as the trigger's `⌘K`: an instruction to press a key is one a reader on a phone cannot follow. `closeLabel`'s button is deliberately not hidden with them — on exactly those devices it is the only pointer route out of the dialog.
|
|
768
|
+
|
|
762
769
|
**`pageSize` replaced `maxResults` in 0.4.0**, and the meaning changed with the name: `maxResults` was a hard ceiling of 8 that made results unreachable on a six-page site, and the live region announced the slice as though it were the total. `pageSize` is a window — every match is reachable by scrolling, and the count announced is the real one.
|
|
763
770
|
|
|
764
771
|
`resultCountLabels` is keyed by plural category rather than being a singular and a plural, because most languages are not English: Polish takes four forms and Arabic six. `Intl.PluralRules` picks, and a category you do not list falls back to `other`.
|
|
@@ -95,6 +95,29 @@ interface SearchDialogProps {
|
|
|
95
95
|
* `{query}` is replaced with what the reader typed.
|
|
96
96
|
*/
|
|
97
97
|
emptyLabel?: string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The arrow-keys hint in the footer. Defaults to `'Select'`.
|
|
100
|
+
*
|
|
101
|
+
* The key-caps beside it are glyphs and are not translatable — an arrow is an
|
|
102
|
+
* arrow, and `Esc` is `Esc` on a Portuguese keyboard. These props are the
|
|
103
|
+
* verbs, which are not.
|
|
104
|
+
*/
|
|
105
|
+
selectLabel?: string | undefined;
|
|
106
|
+
/** The Enter hint in the footer. Defaults to `'Open'`. */
|
|
107
|
+
openLabel?: string | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* The footer's dismiss control. Defaults to `'Close'`.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ THIS REPLACES A BUTTON THAT SAID `Close` IN HARDCODED ENGLISH — the one
|
|
112
|
+
* string in this package that was never lifted to a prop, in the one dialog a
|
|
113
|
+
* reader cannot leave without it.
|
|
114
|
+
*
|
|
115
|
+
* It names a real button rather than a third hint: under `pointer: coarse`
|
|
116
|
+
* the two hints beside it are hidden, because an instruction to press Esc is
|
|
117
|
+
* one a reader on a phone cannot follow — and that leaves this as the only
|
|
118
|
+
* pointer route out of the dialog.
|
|
119
|
+
*/
|
|
120
|
+
closeLabel?: string | undefined;
|
|
98
121
|
/**
|
|
99
122
|
* The live region's announcement, by plural category. `{count}` is the total.
|
|
100
123
|
*
|
|
@@ -151,6 +174,6 @@ interface SearchDialogProps {
|
|
|
151
174
|
* portalled to `document.body`, so a navbar's stacking context cannot trap
|
|
152
175
|
* it behind the page.
|
|
153
176
|
*/
|
|
154
|
-
declare function SearchDialog({ indexUrl, navigate, Link, triggerLabel, placeholder, dialogLabel, pageSize, minQueryLength, debounceMs, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, resultCountLabels, locale }: SearchDialogProps): ReactNode;
|
|
177
|
+
declare function SearchDialog({ indexUrl, navigate, Link, triggerLabel, placeholder, dialogLabel, pageSize, minQueryLength, debounceMs, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, selectLabel, openLabel, closeLabel, resultCountLabels, locale }: SearchDialogProps): ReactNode;
|
|
155
178
|
//#endregion
|
|
156
179
|
export { SearchDialog, SearchDialogProps };
|
|
@@ -26,7 +26,7 @@ const FOCUSABLE_SELECTOR = [
|
|
|
26
26
|
* portalled to `document.body`, so a navbar's stacking context cannot trap
|
|
27
27
|
* it behind the page.
|
|
28
28
|
*/
|
|
29
|
-
function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", placeholder = "Search documentation", dialogLabel = "Search documentation", pageSize = 20, minQueryLength = 2, debounceMs = 120, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, resultCountLabels, locale }) {
|
|
29
|
+
function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", placeholder = "Search documentation", dialogLabel = "Search documentation", pageSize = 20, minQueryLength = 2, debounceMs = 120, className, miniSearchOptions, hintLabel, shortQueryLabel, loadingLabel, errorLabel, emptyLabel, selectLabel = "Select", openLabel = "Open", closeLabel = "Close", resultCountLabels, locale }) {
|
|
30
30
|
const [isOpen, setIsOpen] = useState(false);
|
|
31
31
|
const [query, setQuery] = useState("");
|
|
32
32
|
const [hits, setHits] = useState([]);
|
|
@@ -53,7 +53,18 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
53
53
|
const movedByKeyboard = useRef(false);
|
|
54
54
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
55
55
|
const [status, setStatus] = useState("idle");
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* The trigger's shortcut, split rather than held as one string.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ THE `⌘` NEEDS ITS OWN `font-size` AND CSS CANNOT SELECT A CHARACTER.
|
|
60
|
+
* Measured in the shipped mono stack at 12px, its ink is 6.39px tall against
|
|
61
|
+
* the `K`'s 8.75px — so as one string the symbol sits visibly short of the
|
|
62
|
+
* letter beside it. Two nodes is the only way to scale one and not the other.
|
|
63
|
+
*
|
|
64
|
+
* `null` until the effect below resolves the platform: reading it during
|
|
65
|
+
* render would disagree with the server's markup and break hydration.
|
|
66
|
+
*/
|
|
67
|
+
const [shortcut, setShortcut] = useState(null);
|
|
57
68
|
const dialogRef = useRef(null);
|
|
58
69
|
const triggerRef = useRef(null);
|
|
59
70
|
const inputRef = useRef(null);
|
|
@@ -126,7 +137,10 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
126
137
|
}, [isOpen, closeDialog]);
|
|
127
138
|
useEffect(() => {
|
|
128
139
|
const isApple = /mac|iphone|ipad|ipod/i.test(navigator.userAgent);
|
|
129
|
-
|
|
140
|
+
setShortcut({
|
|
141
|
+
modifier: isApple ? "⌘" : "Ctrl",
|
|
142
|
+
isSymbol: isApple
|
|
143
|
+
});
|
|
130
144
|
}, []);
|
|
131
145
|
useEffect(() => {
|
|
132
146
|
if (isOpen) {
|
|
@@ -267,13 +281,21 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
267
281
|
onClick: openDialog,
|
|
268
282
|
onPointerEnter: warmIndex,
|
|
269
283
|
onFocus: warmIndex,
|
|
270
|
-
children: [
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
children: [
|
|
285
|
+
/* @__PURE__ */ jsx(SearchGlyph, {}),
|
|
286
|
+
/* @__PURE__ */ jsx("span", {
|
|
287
|
+
className: "wave-docs-search-trigger-label",
|
|
288
|
+
children: triggerLabel
|
|
289
|
+
}),
|
|
290
|
+
shortcut === null ? null : /* @__PURE__ */ jsxs("kbd", {
|
|
291
|
+
className: "wave-docs-search-trigger-kbd",
|
|
292
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
293
|
+
className: "wave-docs-search-trigger-mod",
|
|
294
|
+
"data-symbol": shortcut.isSymbol ? "" : void 0,
|
|
295
|
+
children: shortcut.modifier
|
|
296
|
+
}), shortcut.isSymbol ? "K" : " K"]
|
|
297
|
+
})
|
|
298
|
+
]
|
|
277
299
|
}), isOpen ? createPortal(/* @__PURE__ */ jsx("div", {
|
|
278
300
|
className: "wave-docs-search-backdrop",
|
|
279
301
|
onMouseDown: (event) => {
|
|
@@ -288,7 +310,7 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
288
310
|
children: [
|
|
289
311
|
/* @__PURE__ */ jsxs("div", {
|
|
290
312
|
className: "wave-docs-search-input-row",
|
|
291
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
313
|
+
children: [/* @__PURE__ */ jsx(SearchGlyph, {}), /* @__PURE__ */ jsx("input", {
|
|
292
314
|
ref: inputRef,
|
|
293
315
|
className: "wave-docs-search-input",
|
|
294
316
|
type: "text",
|
|
@@ -305,11 +327,6 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
305
327
|
autoComplete: "off",
|
|
306
328
|
autoCorrect: "off",
|
|
307
329
|
spellCheck: false
|
|
308
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
309
|
-
type: "button",
|
|
310
|
-
className: "wave-docs-search-close",
|
|
311
|
-
onClick: closeDialog,
|
|
312
|
-
children: "Close"
|
|
313
330
|
})]
|
|
314
331
|
}),
|
|
315
332
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -346,11 +363,82 @@ function SearchDialog({ indexUrl, navigate, Link, triggerLabel = "Search", place
|
|
|
346
363
|
},
|
|
347
364
|
resultCountLabels,
|
|
348
365
|
locale
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ jsxs("div", {
|
|
368
|
+
className: "wave-docs-search-footer",
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ jsxs("span", {
|
|
371
|
+
className: "wave-docs-search-hint",
|
|
372
|
+
"aria-hidden": "true",
|
|
373
|
+
children: [
|
|
374
|
+
/* @__PURE__ */ jsx("kbd", {
|
|
375
|
+
className: "wave-docs-search-kbd",
|
|
376
|
+
children: "↑"
|
|
377
|
+
}),
|
|
378
|
+
/* @__PURE__ */ jsx("kbd", {
|
|
379
|
+
className: "wave-docs-search-kbd",
|
|
380
|
+
children: "↓"
|
|
381
|
+
}),
|
|
382
|
+
selectLabel
|
|
383
|
+
]
|
|
384
|
+
}),
|
|
385
|
+
/* @__PURE__ */ jsxs("span", {
|
|
386
|
+
className: "wave-docs-search-hint",
|
|
387
|
+
"aria-hidden": "true",
|
|
388
|
+
children: [/* @__PURE__ */ jsx("kbd", {
|
|
389
|
+
className: "wave-docs-search-kbd",
|
|
390
|
+
children: "↵"
|
|
391
|
+
}), openLabel]
|
|
392
|
+
}),
|
|
393
|
+
/* @__PURE__ */ jsxs("button", {
|
|
394
|
+
type: "button",
|
|
395
|
+
className: "wave-docs-search-close",
|
|
396
|
+
"aria-label": closeLabel,
|
|
397
|
+
"aria-keyshortcuts": "Escape",
|
|
398
|
+
onClick: closeDialog,
|
|
399
|
+
children: [/* @__PURE__ */ jsx("kbd", {
|
|
400
|
+
className: "wave-docs-search-kbd",
|
|
401
|
+
children: "Esc"
|
|
402
|
+
}), /* @__PURE__ */ jsx("span", { children: closeLabel })]
|
|
403
|
+
})
|
|
404
|
+
]
|
|
349
405
|
})
|
|
350
406
|
]
|
|
351
407
|
})
|
|
352
408
|
}), document.body) : null] });
|
|
353
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* The magnifier, on the trigger and in the dialog's input row.
|
|
412
|
+
*
|
|
413
|
+
* Decorative in both places: the trigger carries its name in `aria-label` and
|
|
414
|
+
* the input carries its own, so this glyph would only ever repeat a word that
|
|
415
|
+
* is already there — announced as "search Search" and, on some engines, as the
|
|
416
|
+
* name of the character.
|
|
417
|
+
*
|
|
418
|
+
* Inline SVG rather than a `::before` glyph, matching every other icon in this
|
|
419
|
+
* package: generated content is announced by some screen-reader and browser
|
|
420
|
+
* pairs, which is the one thing `aria-hidden` cannot take back.
|
|
421
|
+
*/
|
|
422
|
+
function SearchGlyph() {
|
|
423
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
424
|
+
className: "wave-docs-search-glyph",
|
|
425
|
+
"aria-hidden": "true",
|
|
426
|
+
focusable: "false",
|
|
427
|
+
viewBox: "0 0 24 24",
|
|
428
|
+
width: "16",
|
|
429
|
+
height: "16",
|
|
430
|
+
fill: "none",
|
|
431
|
+
stroke: "currentColor",
|
|
432
|
+
strokeWidth: "2",
|
|
433
|
+
strokeLinecap: "round",
|
|
434
|
+
strokeLinejoin: "round",
|
|
435
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
436
|
+
cx: "11",
|
|
437
|
+
cy: "11",
|
|
438
|
+
r: "7"
|
|
439
|
+
}), /* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" })]
|
|
440
|
+
});
|
|
441
|
+
}
|
|
354
442
|
/** One result row: a real link, so middle-click and "open in new tab" work. */
|
|
355
443
|
function SearchResultOption({ hit, id, isActive, setSize, posInSet, onActivate, onSelect, Link }) {
|
|
356
444
|
function handleClick(event) {
|
package/dist/react/sidebar.js
CHANGED
|
@@ -15,6 +15,24 @@ function normalizeHref(href) {
|
|
|
15
15
|
function isActiveHref(pathname, href) {
|
|
16
16
|
return normalizeHref(pathname) === normalizeHref(href);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* `{ [key]: true }` for every group the active page lives inside, keyed the way
|
|
20
|
+
* the tree renders — `${prefix}-${index}`, index counted over *all* siblings
|
|
21
|
+
* because that is what `NavList` does.
|
|
22
|
+
*
|
|
23
|
+
* Shaped as the state itself rather than as a list of keys so both callers can
|
|
24
|
+
* spread it: it seeds the map on mount and merges into it on every navigation,
|
|
25
|
+
* and neither needs a loop of its own.
|
|
26
|
+
*/
|
|
27
|
+
function openAlong(nodes, pathname, prefix, into = {}) {
|
|
28
|
+
nodes.forEach((node, index) => {
|
|
29
|
+
if (node.type !== "group" || !containsActive(node, pathname)) return;
|
|
30
|
+
const key = `${prefix}-${index}`;
|
|
31
|
+
into[key] = true;
|
|
32
|
+
openAlong(node.children, pathname, key, into);
|
|
33
|
+
});
|
|
34
|
+
return into;
|
|
35
|
+
}
|
|
18
36
|
/** Whether the active page lives anywhere under this node. */
|
|
19
37
|
function containsActive(node, pathname) {
|
|
20
38
|
switch (node.type) {
|
|
@@ -74,11 +92,14 @@ function DocsSidebar({ nav, pathname, Link, label = "Docs", expandGroup, collaps
|
|
|
74
92
|
externalLink: externalLink ?? DEFAULT_SIDEBAR_LABELS.externalLink
|
|
75
93
|
};
|
|
76
94
|
const baseId = useId();
|
|
77
|
-
const [toggled, setToggled] = useState(
|
|
95
|
+
const [toggled, setToggled] = useState(() => openAlong(nav, pathname, baseId));
|
|
78
96
|
const lastPathname = useRef(pathname);
|
|
79
97
|
if (lastPathname.current !== pathname) {
|
|
80
98
|
lastPathname.current = pathname;
|
|
81
|
-
setToggled({
|
|
99
|
+
setToggled((previous) => ({
|
|
100
|
+
...previous,
|
|
101
|
+
...openAlong(nav, pathname, baseId)
|
|
102
|
+
}));
|
|
82
103
|
}
|
|
83
104
|
const handleToggle = (key, isOpen) => {
|
|
84
105
|
setToggled((previous) => ({
|
package/dist/styles.css
CHANGED
|
@@ -153,7 +153,7 @@
|
|
|
153
153
|
* of padding on every side — so setting this is the whole of resizing the
|
|
154
154
|
* control, and the hit area grows with it rather than being a second number
|
|
155
155
|
* to keep in step. */
|
|
156
|
-
--wave-docs-trigger-width:
|
|
156
|
+
--wave-docs-trigger-width: 1rem;
|
|
157
157
|
--wave-docs-toc-width: 15rem;
|
|
158
158
|
|
|
159
159
|
/*
|
|
@@ -1768,14 +1768,28 @@
|
|
|
1768
1768
|
margin-block: 1rem 0.25rem;
|
|
1769
1769
|
}
|
|
1770
1770
|
|
|
1771
|
+
/*
|
|
1772
|
+
* ⚠️ NO `text-transform: uppercase`, AND IT USED TO HAVE ONE.
|
|
1773
|
+
*
|
|
1774
|
+
* A separator's text is authored — `"---Reference---"` in someone's
|
|
1775
|
+
* `meta.json` — so uppercasing it is this package rewriting words it does not
|
|
1776
|
+
* own, in a language it cannot read. Portuguese `Referência` came out as
|
|
1777
|
+
* `REFERÊNCIA`; Turkish loses the dotted and dotless `i` to each other, and
|
|
1778
|
+
* no CJK script has a case to transform at all, so those authors got the
|
|
1779
|
+
* `letter-spacing` and none of the effect it was there to rescue. And it
|
|
1780
|
+
* could not be turned off — the string is a prop, but its shape was ours.
|
|
1781
|
+
*
|
|
1782
|
+
* The job the caps were doing — read as a divider, not as a row — is already
|
|
1783
|
+
* done by the size, the weight and the subtle colour, none of which touch a
|
|
1784
|
+
* single character. `letter-spacing` went with them: it exists to make caps
|
|
1785
|
+
* legible, and on sentence case it only reads as loose.
|
|
1786
|
+
*/
|
|
1771
1787
|
.wave-docs-sidebar__separator {
|
|
1772
1788
|
display: block;
|
|
1773
1789
|
padding-inline: 0.5rem;
|
|
1774
1790
|
color: var(--wave-docs-fg-subtle);
|
|
1775
1791
|
font-size: 0.75rem;
|
|
1776
1792
|
font-weight: 650;
|
|
1777
|
-
letter-spacing: 0.06em;
|
|
1778
|
-
text-transform: uppercase;
|
|
1779
1793
|
}
|
|
1780
1794
|
|
|
1781
1795
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -2607,7 +2621,12 @@
|
|
|
2607
2621
|
.wave-docs-layout__sidebar-trigger::before {
|
|
2608
2622
|
content: '';
|
|
2609
2623
|
width: var(--wave-docs-trigger-width);
|
|
2610
|
-
|
|
2624
|
+
/*
|
|
2625
|
+
* 16 x 56 — a 3.5:1 grip. It was 20 x 80, which at the old width read as a
|
|
2626
|
+
* slab; narrowing it made the length obvious. The floor is 18px, the span
|
|
2627
|
+
* of the three dots, and nothing here scales with the pill: see `::after`.
|
|
2628
|
+
*/
|
|
2629
|
+
height: 3.5rem;
|
|
2611
2630
|
border-radius: var(--wave-docs-radius-sm);
|
|
2612
2631
|
background-color: var(--wave-docs-border-strong);
|
|
2613
2632
|
}
|
|
@@ -2631,11 +2650,46 @@
|
|
|
2631
2650
|
translate: -50% -50%;
|
|
2632
2651
|
border-radius: 50%;
|
|
2633
2652
|
background: currentcolor;
|
|
2653
|
+
/*
|
|
2654
|
+
* ⚠️ THE DOTS DO NOT SCALE WITH THE PILL. They are `box-shadow` offsets on
|
|
2655
|
+
* one element, so ±7px is 18px of ink whatever `--wave-docs-trigger-width`
|
|
2656
|
+
* or the pill's height happen to be. Shorten the pill below 18px and the
|
|
2657
|
+
* outer two sit outside it, top and bottom — measured, not guessed.
|
|
2658
|
+
*/
|
|
2634
2659
|
box-shadow:
|
|
2635
2660
|
0 -7px 0 currentcolor,
|
|
2636
2661
|
0 7px 0 currentcolor;
|
|
2637
2662
|
}
|
|
2638
2663
|
|
|
2664
|
+
/*
|
|
2665
|
+
* Faded at rest, full strength under a pointer or a caret — the same
|
|
2666
|
+
* treatment as the tree's markers, and for the same reason: this is a handle,
|
|
2667
|
+
* not a thing to look at while reading.
|
|
2668
|
+
*
|
|
2669
|
+
* ⚠️ BOTH MARKS, NOT THE PILL ALONE. Fading `::before` by itself leaves the
|
|
2670
|
+
* dots at full strength on a washed-out slab, which reads louder than the
|
|
2671
|
+
* solid grip it replaced — the opposite of the point.
|
|
2672
|
+
*
|
|
2673
|
+
* ⚠️ AND NOT ON THE BUTTON ITSELF, which would take the focus ring down with
|
|
2674
|
+
* it: `opacity` applies to the whole element, outline included, so a keyboard
|
|
2675
|
+
* reader would get a 40% indicator on a control they had just moved to.
|
|
2676
|
+
*
|
|
2677
|
+
* ⚠️ THE TAP TARGET IS UNTOUCHED BY ANY OF THIS. The `<button>` is the entire
|
|
2678
|
+
* strip and runs the height of the column; the pill is paint. Its 80px was
|
|
2679
|
+
* only ever ink, which is why 32px costs nothing a finger can feel.
|
|
2680
|
+
*/
|
|
2681
|
+
.wave-docs-layout__sidebar-trigger::before,
|
|
2682
|
+
.wave-docs-layout__sidebar-trigger::after {
|
|
2683
|
+
opacity: 0.4;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
.wave-docs-layout__sidebar-trigger:hover::before,
|
|
2687
|
+
.wave-docs-layout__sidebar-trigger:hover::after,
|
|
2688
|
+
.wave-docs-layout__sidebar-trigger:focus-visible::before,
|
|
2689
|
+
.wave-docs-layout__sidebar-trigger:focus-visible::after {
|
|
2690
|
+
opacity: 1;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2639
2693
|
/*
|
|
2640
2694
|
* ⚠️ THE STRIP'S HOVER LIGHTS THE BUTTON TOO, AND THAT IS WHAT MAKES IT ONE
|
|
2641
2695
|
* CONTROL. Hovering 44px of strip while the button inside it stays grey reads
|
|
@@ -2667,7 +2721,8 @@
|
|
|
2667
2721
|
*/
|
|
2668
2722
|
transition:
|
|
2669
2723
|
background-color 120ms ease-out,
|
|
2670
|
-
color 120ms ease-out
|
|
2724
|
+
color 120ms ease-out,
|
|
2725
|
+
opacity 120ms ease-out;
|
|
2671
2726
|
}
|
|
2672
2727
|
}
|
|
2673
2728
|
|
|
@@ -2823,14 +2878,30 @@
|
|
|
2823
2878
|
* ------------------------------------------------------------------------ */
|
|
2824
2879
|
|
|
2825
2880
|
@layer components {
|
|
2881
|
+
/*
|
|
2882
|
+
* ⚠️ THE INLINE PADDING AND THE GAP ARE THE NAVIGATION'S, NOT THIS BUTTON'S,
|
|
2883
|
+
* AND THE `- 1px` IS THE WHOLE REASON THEY LOOK THE SAME.
|
|
2884
|
+
*
|
|
2885
|
+
* The trigger sits directly above the tree, so its magnifier is the first
|
|
2886
|
+
* thing in the same column as every folder and page marker below it, and its
|
|
2887
|
+
* label starts the same column as every title. Both were off — measured at
|
|
2888
|
+
* 1280px, the glyph by 3px and the label by 11px — because this button was
|
|
2889
|
+
* spaced as a standalone control: 10px of padding against the rows' 8px, and
|
|
2890
|
+
* a 16px gap against their 8px.
|
|
2891
|
+
*
|
|
2892
|
+
* The rows carry no border and this does, so matching `0.5rem` outright would
|
|
2893
|
+
* leave the glyph 1px out. `calc(0.5rem - 1px)` puts the *content* edge where
|
|
2894
|
+
* theirs is, which is the edge a reader sees. Block padding is untouched: it
|
|
2895
|
+
* sets this control's height, and nothing below it is the same height.
|
|
2896
|
+
*/
|
|
2826
2897
|
.wave-docs-search-trigger {
|
|
2827
2898
|
display: flex;
|
|
2828
2899
|
align-items: center;
|
|
2829
2900
|
justify-content: space-between;
|
|
2830
|
-
gap:
|
|
2901
|
+
gap: 0.5rem;
|
|
2831
2902
|
width: 100%;
|
|
2832
2903
|
max-width: 20rem;
|
|
2833
|
-
padding: 0.4375rem 0.
|
|
2904
|
+
padding: 0.4375rem calc(0.5rem - 1px);
|
|
2834
2905
|
border: 1px solid var(--wave-docs-border);
|
|
2835
2906
|
border-radius: var(--wave-docs-radius);
|
|
2836
2907
|
background: var(--wave-docs-bg-subtle);
|
|
@@ -2871,14 +2942,26 @@
|
|
|
2871
2942
|
outline-offset: 2px;
|
|
2872
2943
|
}
|
|
2873
2944
|
|
|
2874
|
-
/* The label is the flexible half of the trigger; the
|
|
2875
|
-
*
|
|
2945
|
+
/* The label is the flexible half of the trigger; neither the magnifier before
|
|
2946
|
+
* it nor the shortcut hint after it may be the thing squeezed out at narrow
|
|
2947
|
+
* widths. `flex: 1 1 auto` because the row is `space-between` and three
|
|
2948
|
+
* items would otherwise centre the middle one, leaving a gap on both sides of
|
|
2949
|
+
* a short label. */
|
|
2876
2950
|
.wave-docs-search-trigger-label {
|
|
2951
|
+
flex: 1 1 auto;
|
|
2952
|
+
min-width: 0;
|
|
2877
2953
|
overflow: hidden;
|
|
2878
2954
|
text-overflow: ellipsis;
|
|
2879
2955
|
white-space: nowrap;
|
|
2880
2956
|
}
|
|
2881
2957
|
|
|
2958
|
+
/* Quieter than the words either side of it, in the trigger and in the dialog
|
|
2959
|
+
* alike — it is a category marker, not a control. */
|
|
2960
|
+
.wave-docs-search-glyph {
|
|
2961
|
+
flex: none;
|
|
2962
|
+
color: var(--wave-docs-fg-subtle);
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2882
2965
|
/*
|
|
2883
2966
|
* ⚠️ HIDDEN WHERE THE SHORTCUT CANNOT BE PRESSED, WHICH IS NOT A WIDTH
|
|
2884
2967
|
* QUESTION. `⌘K` is an instruction, and on a phone or a tablet it is one the
|
|
@@ -2891,16 +2974,105 @@
|
|
|
2891
2974
|
}
|
|
2892
2975
|
}
|
|
2893
2976
|
|
|
2894
|
-
.wave-docs-search-trigger-kbd
|
|
2977
|
+
.wave-docs-search-trigger-kbd,
|
|
2978
|
+
.wave-docs-search-kbd {
|
|
2895
2979
|
flex: none;
|
|
2896
|
-
padding: 0.05rem 0.35rem;
|
|
2897
|
-
border: 1px solid var(--wave-docs-border-strong);
|
|
2898
|
-
border-radius: var(--wave-docs-radius-sm);
|
|
2899
2980
|
color: var(--wave-docs-fg-subtle);
|
|
2900
2981
|
font-family: var(--wave-docs-font-mono);
|
|
2901
2982
|
font-size: 0.75rem;
|
|
2902
2983
|
}
|
|
2903
2984
|
|
|
2985
|
+
/*
|
|
2986
|
+
* The footer's caps are drawn as keys; the trigger's shortcut is not.
|
|
2987
|
+
*
|
|
2988
|
+
* They were one rule, and the trigger's read as a chip on a surface that is
|
|
2989
|
+
* already a chip — a bordered box inside a bordered box, sharing its fill,
|
|
2990
|
+
* for a hint nobody clicks. The footer's caps sit on the flat bottom of the
|
|
2991
|
+
* dialog and are the only thing there that has to look pressable.
|
|
2992
|
+
*
|
|
2993
|
+
* No `background` was ever set on either: the trigger's own
|
|
2994
|
+
* `--wave-docs-bg-subtle` showed through, which is what made the border read
|
|
2995
|
+
* as a filled shape. Removing the border is what removes the shape.
|
|
2996
|
+
*/
|
|
2997
|
+
.wave-docs-search-kbd {
|
|
2998
|
+
padding: 0.05rem 0.35rem;
|
|
2999
|
+
border: 1px solid var(--wave-docs-border-strong);
|
|
3000
|
+
border-radius: var(--wave-docs-radius-sm);
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
/*
|
|
3004
|
+
* ⚠️ THE LABEL'S SIZE, AND IT IS THE CAPS THAT HAD TO MATCH — NOT THE NUMBER.
|
|
3005
|
+
*
|
|
3006
|
+
* `Search` is `ui-sans-serif` and this is `ui-monospace`, and the same
|
|
3007
|
+
* `font-size` in two families does not give you the same letter. Measured at
|
|
3008
|
+
* 14px: the label's `S` carries 10.33px of ink and the mono `K` carries
|
|
3009
|
+
* 10.21px. A tenth of a pixel — invisible until you look, and then it is the
|
|
3010
|
+
* only thing you can see.
|
|
3011
|
+
*
|
|
3012
|
+
* `1.012em` is that tenth. Mono here draws 0.7292px of cap per px of
|
|
3013
|
+
* `font-size`, so 10.33px of cap needs 14.17px, which is 1.012 x the 14px it
|
|
3014
|
+
* sits beside. `em` rather than a `rem` constant so it tracks the label if
|
|
3015
|
+
* the trigger's size ever moves.
|
|
3016
|
+
*
|
|
3017
|
+
* ⚠️ IT IS A MEASURED CONSTANT AND IT BELONGS TO THESE TWO FAMILIES. Change
|
|
3018
|
+
* `--wave-docs-font-mono` or `--wave-docs-font-sans` and it is wrong — not
|
|
3019
|
+
* broken, just quietly a tenth of a pixel out again. Re-measure the caps, do
|
|
3020
|
+
* not re-derive from the declaration.
|
|
3021
|
+
*
|
|
3022
|
+
* Not in the shared rule above: the footer's caps sit in a 12px footer and
|
|
3023
|
+
* belong at 12px. This is the only `<kbd>` standing beside body-sized text.
|
|
3024
|
+
*/
|
|
3025
|
+
.wave-docs-search-trigger-kbd {
|
|
3026
|
+
font-size: 1.012em;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
/*
|
|
3030
|
+
* ⚠️ THE SYMBOL IS SCALED AND THE WORD IS NOT — which is why this is a
|
|
3031
|
+
* separate element rather than one string in the `<kbd>`.
|
|
3032
|
+
*
|
|
3033
|
+
* Measured in the shipped mono stack: `⌘` carries 6.39px of ink per 8.75px of
|
|
3034
|
+
* `K`, so at a shared `font-size` it sits a third short of the letter next to
|
|
3035
|
+
* it. `1.369em` is exactly that ratio, and it makes the two the same height.
|
|
3036
|
+
*
|
|
3037
|
+
* ⚠️ NOT A HAIR TALLER, WHICH IS WHERE THIS FIRST LANDED. `1.45em` gave the
|
|
3038
|
+
* symbol 6% on the `K` — invisible at 12px, and at this size 0.6px of ink
|
|
3039
|
+
* standing above the cap line. It made the whole badge read as *bigger type*
|
|
3040
|
+
* than the label beside it: measured against the word `Search`, 88 device px
|
|
3041
|
+
* of ink against 85, its top three device px higher and its centre 1.5 out.
|
|
3042
|
+
* Same letter height, louder cluster. The eye compares the clusters.
|
|
3043
|
+
*
|
|
3044
|
+
* `Ctrl` gets none of it: it is a word set in the same face as the `K`, and
|
|
3045
|
+
* scaling it would make the hint shout.
|
|
3046
|
+
*/
|
|
3047
|
+
.wave-docs-search-trigger-mod[data-symbol] {
|
|
3048
|
+
font-size: 1.369em;
|
|
3049
|
+
/*
|
|
3050
|
+
* ⚠️ `0`, NOT `1` — THIS GLYPH MUST NOT SIZE THE LINE IT SITS ON. A line
|
|
3051
|
+
* box is as tall as the tallest inline box in it, so at `line-height: 1`
|
|
3052
|
+
* this 20.5px symbol made the `<kbd>` 20.55px tall against the label's 17,
|
|
3053
|
+
* and the two were centred as flex items by their *boxes*. Same cap height,
|
|
3054
|
+
* 1.9px apart on screen — measured off an 8x render, invisible in the CSS.
|
|
3055
|
+
*
|
|
3056
|
+
* At `0` the symbol contributes no height, the `<kbd>` keeps the line box
|
|
3057
|
+
* its own font gives it, and the `K` centres with the `S` beside it. The
|
|
3058
|
+
* glyph still paints in full: `line-height` bounds the line, not the ink.
|
|
3059
|
+
*/
|
|
3060
|
+
line-height: 0;
|
|
3061
|
+
/*
|
|
3062
|
+
* ⚠️ AND IT HAS TO BE MOVED DOWN, WHICH `font-size` ALONE WILL NEVER DO.
|
|
3063
|
+
*
|
|
3064
|
+
* `⌘` is drawn around the font's mathematical axis, not standing on the
|
|
3065
|
+
* baseline like a capital. Measured at this size: its ink runs from 11.24px
|
|
3066
|
+
* above the baseline to 1.98px *above* it — a centre 6.61px up — while the
|
|
3067
|
+
* `K` beside it runs 8.75px to 0, centred 4.38px up. Aligned by baseline,
|
|
3068
|
+
* as inline text is, the symbol therefore floats 2.24px high.
|
|
3069
|
+
*
|
|
3070
|
+
* `-0.13em` of this element's own 17.4px is 2.26px, which puts the two ink
|
|
3071
|
+
* centres within a rounding error of each other.
|
|
3072
|
+
*/
|
|
3073
|
+
vertical-align: -0.13em;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
2904
3076
|
.wave-docs-search-backdrop {
|
|
2905
3077
|
position: fixed;
|
|
2906
3078
|
inset: 0;
|
|
@@ -2989,21 +3161,94 @@
|
|
|
2989
3161
|
color: var(--wave-docs-fg-subtle);
|
|
2990
3162
|
}
|
|
2991
3163
|
|
|
2992
|
-
|
|
3164
|
+
/*
|
|
3165
|
+
* The keyboard footer.
|
|
3166
|
+
*
|
|
3167
|
+
* ⚠️ `flex: none`, BECAUSE THE LIST ABOVE IT IS `flex: 1 1 auto`. The dialog
|
|
3168
|
+
* is a column with a ceiling, and the results are what takes the slack and
|
|
3169
|
+
* scrolls; without this the footer is a second growable item and the two
|
|
3170
|
+
* share the overflow — a footer that drifts down the dialog as results
|
|
3171
|
+
* arrive, and a list that never quite reaches it.
|
|
3172
|
+
*/
|
|
3173
|
+
.wave-docs-search-footer {
|
|
2993
3174
|
flex: none;
|
|
2994
|
-
|
|
2995
|
-
|
|
3175
|
+
display: flex;
|
|
3176
|
+
align-items: center;
|
|
3177
|
+
gap: 1rem;
|
|
3178
|
+
padding: 0.5rem 0.75rem;
|
|
3179
|
+
border-block-start: 1px solid var(--wave-docs-border);
|
|
3180
|
+
color: var(--wave-docs-fg-subtle);
|
|
3181
|
+
font-size: 0.75rem;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
.wave-docs-search-hint,
|
|
3185
|
+
.wave-docs-search-close {
|
|
3186
|
+
display: inline-flex;
|
|
3187
|
+
align-items: center;
|
|
3188
|
+
gap: 0.375rem;
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
/*
|
|
3192
|
+
* ⚠️ A NEGATIVE MARGIN, BECAUSE `gap` CANNOT VARY PER PAIR. `↑` and `↓` are
|
|
3193
|
+
* one instruction and read as one object; the gap that separates a cap from
|
|
3194
|
+
* the word beside it is too wide between two caps that belong together. A
|
|
3195
|
+
* flex container has a single gap, so the only ways to tighten one pair are
|
|
3196
|
+
* an extra wrapper element or this — and a wrapper on every hint is markup
|
|
3197
|
+
* bought for spacing.
|
|
3198
|
+
*
|
|
3199
|
+
* `-0.25rem` against the `0.375rem` above leaves `0.125rem` between them.
|
|
3200
|
+
*/
|
|
3201
|
+
.wave-docs-search-kbd + .wave-docs-search-kbd {
|
|
3202
|
+
margin-inline-start: -0.25rem;
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
/*
|
|
3206
|
+
* The only cap holding a word rather than a glyph, and three letters at the
|
|
3207
|
+
* arrows' size made it the loudest thing in the footer. `line-height` pins
|
|
3208
|
+
* the box so it still sits at the same height as the caps beside it —
|
|
3209
|
+
* without it the smaller text shrinks the content box and the cap rides
|
|
3210
|
+
* high in a row of taller ones.
|
|
3211
|
+
*/
|
|
3212
|
+
.wave-docs-search-close .wave-docs-search-kbd {
|
|
3213
|
+
font-size: 0.625rem;
|
|
3214
|
+
/* Solved, not guessed: the cap is 3.6px of border and padding plus its own
|
|
3215
|
+
* line box, and the 12px caps beside it measure 17.6px. 10px x 1.4 = 14,
|
|
3216
|
+
* and 14 + 3.6 is 17.6 — all four exactly the same height. */
|
|
3217
|
+
line-height: 1.4;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
/*
|
|
3221
|
+
* ⚠️ HIDDEN WHERE THE KEY CANNOT BE PRESSED — the same query, and the same
|
|
3222
|
+
* reason, as the trigger's `⌘K`. An instruction to press an arrow key is one
|
|
3223
|
+
* a reader on a phone cannot follow, and a 10-inch tablet at 1024px would
|
|
3224
|
+
* keep it under any width rule.
|
|
3225
|
+
*
|
|
3226
|
+
* The Close button beside them is deliberately NOT in here. It is the only
|
|
3227
|
+
* pointer dismiss affordance in the dialog, and on exactly the devices this
|
|
3228
|
+
* query names there is no Esc to fall back to.
|
|
3229
|
+
*/
|
|
3230
|
+
@media (hover: none) and (pointer: coarse) {
|
|
3231
|
+
.wave-docs-search-hint {
|
|
3232
|
+
display: none;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
/* Pushed to the far end, away from the two hints — the difference between a
|
|
3237
|
+
* control and a caption, said with position. */
|
|
3238
|
+
.wave-docs-search-close {
|
|
3239
|
+
margin-inline-start: auto;
|
|
3240
|
+
padding: 0.125rem 0.25rem;
|
|
3241
|
+
border: 0;
|
|
2996
3242
|
border-radius: var(--wave-docs-radius-sm);
|
|
2997
3243
|
background: none;
|
|
2998
3244
|
color: var(--wave-docs-fg-muted);
|
|
2999
3245
|
font: inherit;
|
|
3000
|
-
font-size: 0.
|
|
3246
|
+
font-size: 0.75rem;
|
|
3001
3247
|
cursor: pointer;
|
|
3002
3248
|
}
|
|
3003
3249
|
|
|
3004
3250
|
.wave-docs-search-close:hover {
|
|
3005
3251
|
color: var(--wave-docs-fg);
|
|
3006
|
-
border-color: var(--wave-docs-border-strong);
|
|
3007
3252
|
}
|
|
3008
3253
|
|
|
3009
3254
|
/*
|
package/package.json
CHANGED