@waveso/docs 0.1.0 → 0.2.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/CHANGELOG.md +84 -0
- package/README.md +111 -22
- package/dist/docs-error.d.ts +74 -0
- package/dist/docs-error.js +40 -0
- package/dist/frontmatter.d.ts +39 -7
- package/dist/frontmatter.js +51 -24
- package/dist/highlighter.d.ts +2 -2
- package/dist/highlighter.js +3 -2
- package/dist/map-pooled.d.ts +26 -0
- package/dist/map-pooled.js +45 -0
- package/dist/meta.d.ts +7 -3
- package/dist/meta.js +61 -15
- package/dist/next.d.ts +41 -19
- package/dist/next.js +117 -21
- package/dist/plugins/rehype-capture-toc.js +26 -15
- package/dist/plugins/rehype-code-language.d.ts +24 -0
- package/dist/plugins/rehype-code-language.js +48 -0
- package/dist/plugins/rehype-fallback-heading-ids.d.ts +6 -0
- package/dist/plugins/rehype-fallback-heading-ids.js +51 -0
- package/dist/plugins/rehype-flatten-roots.d.ts +7 -0
- package/dist/plugins/rehype-flatten-roots.js +39 -0
- package/dist/plugins/remark-doc-links.d.ts +12 -1
- package/dist/plugins/remark-doc-links.js +147 -20
- package/dist/react/markdown-components.js +71 -6
- package/dist/react/search-dialog.d.ts +23 -7
- package/dist/react/search-dialog.js +46 -29
- package/dist/react/toc.js +28 -5
- package/dist/react/youtube.js +6 -4
- package/dist/render.d.ts +43 -9
- package/dist/render.js +112 -50
- package/dist/search-index.d.ts +32 -14
- package/dist/search-index.js +45 -51
- package/dist/search-options.d.ts +32 -1
- package/dist/search-options.js +66 -3
- package/dist/section-boundary.d.ts +17 -0
- package/dist/section-boundary.js +43 -0
- package/dist/source.d.ts +13 -1
- package/dist/source.js +152 -56
- package/dist/styles.css +236 -90
- package/dist/types.d.ts +41 -27
- package/package.json +13 -12
package/dist/styles.css
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* the classes. Both win, and NOT because of source order — everything this file
|
|
15
15
|
* declares lives in a `@layer`, and unlayered CSS beats every layer regardless
|
|
16
16
|
* of specificity. That distinction is the whole fix: the dark token block is
|
|
17
|
-
* `:root
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* `:root[data-theme='dark']`, which is (0,2,0), so an unlayered `:root` at
|
|
18
|
+
* (0,1,0) would lose no matter where it was loaded — the cascade never gets as
|
|
19
|
+
* far as order. A consumer would have to write `:root:root:root` to outrank us.
|
|
20
20
|
*
|
|
21
21
|
* `@tailwindcss/typography` is deliberately not used. `.prose` pins
|
|
22
22
|
* `max-width: 65ch` — wrong inside a three-column docs shell — and styles
|
|
@@ -24,12 +24,6 @@
|
|
|
24
24
|
* below does the same job without the fight.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
/* Register this package's compiled components as Tailwind sources, so a
|
|
28
|
-
* consumer using Tailwind gets any utility class we ship. It belongs here, not
|
|
29
|
-
* in the app: a consumer-side `@source "../node_modules/@waveso/docs"` hardcodes
|
|
30
|
-
* the relative depth of node_modules and breaks under pnpm's nested store. */
|
|
31
|
-
@source "./";
|
|
32
|
-
|
|
33
27
|
/* Explicit, so the order does not silently depend on which block appears
|
|
34
28
|
* first. `theme` holds the tokens, `base` the element resets, `components`
|
|
35
29
|
* the classes. A consumer's own unlayered CSS outranks all three. */
|
|
@@ -38,10 +32,17 @@
|
|
|
38
32
|
/* ---------------------------------------------------------------------------
|
|
39
33
|
* Tokens
|
|
40
34
|
*
|
|
35
|
+
* ⚠️ DARK IS OPT-IN, NEVER AUTOMATIC. This sheet paints a region of somebody
|
|
36
|
+
* else's page, and it cannot see that page's background. Installing the dark
|
|
37
|
+
* ramp from `prefers-color-scheme` alone put near-white text (1.23:1) on the
|
|
38
|
+
* white page of every light-only site with a /docs section — and on every site
|
|
39
|
+
* using next-themes, whose default `attribute="class"` sets `.dark` and never
|
|
40
|
+
* `data-theme`. So: `[data-theme='dark']` or `.dark` switches, and a host that
|
|
41
|
+
* genuinely wants to follow the OS says so with `[data-theme='system']`.
|
|
42
|
+
*
|
|
41
43
|
* The dark block is spelled twice on purpose: `@media` and an attribute
|
|
42
|
-
* selector cannot be combined into one selector list.
|
|
43
|
-
*
|
|
44
|
-
* `[data-theme='dark']` opts into dark on a light one.
|
|
44
|
+
* selector cannot be combined into one selector list. The two copies are
|
|
45
|
+
* asserted identical by `src/styles.test.ts`.
|
|
45
46
|
* ------------------------------------------------------------------------ */
|
|
46
47
|
|
|
47
48
|
/*
|
|
@@ -56,16 +57,22 @@
|
|
|
56
57
|
* ~0.555 and still clear 4.5:1 on `bg-subtle`; `fg-muted` moved down to keep
|
|
57
58
|
* three visibly distinct steps rather than two-and-a-half.
|
|
58
59
|
*
|
|
59
|
-
* The dark ramp is fg (0.93) / fg-muted (0.72) / fg-subtle (0.
|
|
60
|
+
* The dark ramp is fg (0.93) / fg-muted (0.72) / fg-subtle (0.665), and its
|
|
60
61
|
* `accent-subtle` sits at 0.26 rather than 0.30 — `fg-subtle` paints the search
|
|
61
62
|
* breadcrumb, which lands on `accent-subtle` in the highlighted result, and a
|
|
62
63
|
* lighter tint there would force `fg-subtle` up into `fg-muted`.
|
|
63
64
|
*/
|
|
64
65
|
@layer theme {
|
|
65
66
|
/* Layered so a consumer's unlayered `:root` outranks these without having
|
|
66
|
-
* to out-specify `:root
|
|
67
|
-
*
|
|
67
|
+
* to out-specify `:root[data-theme='dark']`. Inside this layer the usual
|
|
68
|
+
* rules still apply, so the dark blocks keep beating the light one. */
|
|
68
69
|
:root {
|
|
70
|
+
/* Native scrollbars, form controls and the canvas behind an overscroll are
|
|
71
|
+
* painted by the UA, not by us; without this they stay light on a dark
|
|
72
|
+
* page. It is declared alongside the ramp it belongs to, so the two can
|
|
73
|
+
* never disagree. */
|
|
74
|
+
color-scheme: light;
|
|
75
|
+
|
|
69
76
|
--wave-docs-bg: oklch(1 0 0);
|
|
70
77
|
--wave-docs-bg-subtle: oklch(0.985 0.002 260);
|
|
71
78
|
--wave-docs-fg: oklch(0.25 0.02 262);
|
|
@@ -84,7 +91,11 @@
|
|
|
84
91
|
|
|
85
92
|
--wave-docs-code-bg: oklch(0.97 0.003 262);
|
|
86
93
|
--wave-docs-code-fg: oklch(0.32 0.03 300);
|
|
87
|
-
|
|
94
|
+
/* Darker than `bg-subtle`: this one has to read as a surface against the
|
|
95
|
+
* page (1.07:1) rather than merge into it, because it is the only thing
|
|
96
|
+
* separating a code block from the prose around it apart from a 1px
|
|
97
|
+
* border. */
|
|
98
|
+
--wave-docs-code-block-bg: oklch(0.975 0.003 262);
|
|
88
99
|
|
|
89
100
|
/* Each accent is the label colour on its own tinted background, so each one
|
|
90
101
|
* is a 4.5:1 pair in its own right — not merely legible against the page. */
|
|
@@ -94,14 +105,17 @@
|
|
|
94
105
|
--wave-docs-callout-tip-bg: oklch(0.97 0.03 156);
|
|
95
106
|
--wave-docs-callout-important: oklch(0.55 0.19 300);
|
|
96
107
|
--wave-docs-callout-important-bg: oklch(0.97 0.025 300);
|
|
97
|
-
--wave-docs-callout-warning: oklch(0.53 0.14
|
|
98
|
-
--wave-docs-callout-warning-bg: oklch(0.97 0.04
|
|
108
|
+
--wave-docs-callout-warning: oklch(0.53 0.14 80);
|
|
109
|
+
--wave-docs-callout-warning-bg: oklch(0.97 0.04 80);
|
|
99
110
|
--wave-docs-callout-caution: oklch(0.55 0.2 24);
|
|
100
111
|
--wave-docs-callout-caution-bg: oklch(0.97 0.025 24);
|
|
101
112
|
|
|
102
113
|
--wave-docs-radius: 0.5rem;
|
|
103
114
|
--wave-docs-radius-sm: 0.375rem;
|
|
104
|
-
|
|
115
|
+
/* How far below the top of the scrollport an anchored heading parks —
|
|
116
|
+
* i.e. the height of the host's sticky header. Set it to 0 if there is no
|
|
117
|
+
* such header; nothing else in this sheet reads it. */
|
|
118
|
+
--wave-docs-scroll-padding: 4rem;
|
|
105
119
|
--wave-docs-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo,
|
|
106
120
|
Consolas, "Liberation Mono", monospace;
|
|
107
121
|
|
|
@@ -110,13 +124,16 @@
|
|
|
110
124
|
0 0 0 4px var(--wave-docs-accent);
|
|
111
125
|
}
|
|
112
126
|
|
|
127
|
+
/* Only for a host that has explicitly delegated the choice to the OS. */
|
|
113
128
|
@media (prefers-color-scheme: dark) {
|
|
114
|
-
:root
|
|
129
|
+
:root[data-theme='system'] {
|
|
130
|
+
color-scheme: dark;
|
|
131
|
+
|
|
115
132
|
--wave-docs-bg: oklch(0.19 0.015 265);
|
|
116
133
|
--wave-docs-bg-subtle: oklch(0.23 0.015 265);
|
|
117
134
|
--wave-docs-fg: oklch(0.93 0.008 265);
|
|
118
135
|
--wave-docs-fg-muted: oklch(0.72 0.014 265);
|
|
119
|
-
--wave-docs-fg-subtle: oklch(0.
|
|
136
|
+
--wave-docs-fg-subtle: oklch(0.665 0.015 265);
|
|
120
137
|
--wave-docs-border: oklch(0.31 0.014 265);
|
|
121
138
|
--wave-docs-border-strong: oklch(0.4 0.016 265);
|
|
122
139
|
|
|
@@ -135,19 +152,28 @@
|
|
|
135
152
|
--wave-docs-callout-tip-bg: oklch(0.27 0.05 156);
|
|
136
153
|
--wave-docs-callout-important: oklch(0.76 0.14 300);
|
|
137
154
|
--wave-docs-callout-important-bg: oklch(0.28 0.06 300);
|
|
138
|
-
--wave-docs-callout-warning: oklch(0.82 0.14
|
|
139
|
-
--wave-docs-callout-warning-bg: oklch(0.29 0.05
|
|
155
|
+
--wave-docs-callout-warning: oklch(0.82 0.14 80);
|
|
156
|
+
--wave-docs-callout-warning-bg: oklch(0.29 0.05 80);
|
|
140
157
|
--wave-docs-callout-caution: oklch(0.72 0.16 24);
|
|
141
158
|
--wave-docs-callout-caution-bg: oklch(0.28 0.06 24);
|
|
142
159
|
}
|
|
143
160
|
}
|
|
144
161
|
|
|
145
|
-
|
|
162
|
+
/* `.dark` as well as `[data-theme='dark']`: next-themes defaults to
|
|
163
|
+
* `attribute="class"`, and that default is what most consumers ship. */
|
|
164
|
+
:root[data-theme='dark'],
|
|
165
|
+
:root.dark {
|
|
166
|
+
color-scheme: dark;
|
|
167
|
+
|
|
146
168
|
--wave-docs-bg: oklch(0.19 0.015 265);
|
|
147
169
|
--wave-docs-bg-subtle: oklch(0.23 0.015 265);
|
|
148
170
|
--wave-docs-fg: oklch(0.93 0.008 265);
|
|
149
171
|
--wave-docs-fg-muted: oklch(0.72 0.014 265);
|
|
150
|
-
|
|
172
|
+
/* 0.665, not 0.64: `fg-subtle` paints the list markers and heading
|
|
173
|
+
* anchors, and inside a callout it composes against `callout-*-bg`, where
|
|
174
|
+
* 0.64 measured 4.21-4.47:1. It still clears 4.5:1 on `accent-subtle`,
|
|
175
|
+
* which is the constraint pulling it the other way. */
|
|
176
|
+
--wave-docs-fg-subtle: oklch(0.665 0.015 265);
|
|
151
177
|
--wave-docs-border: oklch(0.31 0.014 265);
|
|
152
178
|
--wave-docs-border-strong: oklch(0.4 0.016 265);
|
|
153
179
|
|
|
@@ -166,33 +192,47 @@
|
|
|
166
192
|
--wave-docs-callout-tip-bg: oklch(0.27 0.05 156);
|
|
167
193
|
--wave-docs-callout-important: oklch(0.76 0.14 300);
|
|
168
194
|
--wave-docs-callout-important-bg: oklch(0.28 0.06 300);
|
|
169
|
-
--wave-docs-callout-warning: oklch(0.82 0.14
|
|
170
|
-
--wave-docs-callout-warning-bg: oklch(0.29 0.05
|
|
195
|
+
--wave-docs-callout-warning: oklch(0.82 0.14 80);
|
|
196
|
+
--wave-docs-callout-warning-bg: oklch(0.29 0.05 80);
|
|
171
197
|
--wave-docs-callout-caution: oklch(0.72 0.16 24);
|
|
172
198
|
--wave-docs-callout-caution-bg: oklch(0.28 0.06 24);
|
|
173
199
|
}
|
|
174
|
-
|
|
175
|
-
/* ---------------------------------------------------------------------------
|
|
176
|
-
* Base
|
|
177
|
-
* ------------------------------------------------------------------------ */
|
|
178
200
|
}
|
|
179
201
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* sticky header. Match it to your header's height. */
|
|
184
|
-
scroll-padding-top: var(--wave-docs-header-height);
|
|
185
|
-
}
|
|
202
|
+
/* ---------------------------------------------------------------------------
|
|
203
|
+
* Base
|
|
204
|
+
* ------------------------------------------------------------------------ */
|
|
186
205
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
206
|
+
@layer base {
|
|
207
|
+
/*
|
|
208
|
+
* The ground the ramp is composed against.
|
|
209
|
+
*
|
|
210
|
+
* A foreground token is only as legible as the background under it, and this
|
|
211
|
+
* sheet cannot see the host's. Painting our own containers — never `body`,
|
|
212
|
+
* which belongs to the host — is what makes every ratio in
|
|
213
|
+
* `src/styles.test.ts` a fact about the shipped page rather than about a
|
|
214
|
+
* background nobody declared.
|
|
215
|
+
*/
|
|
216
|
+
.wave-docs-prose,
|
|
217
|
+
.wave-docs-sidebar,
|
|
218
|
+
.wave-docs-toc {
|
|
219
|
+
background: var(--wave-docs-bg);
|
|
220
|
+
color: var(--wave-docs-fg);
|
|
221
|
+
/* Inert unless the container is itself the scrollport, which a sidebar or
|
|
222
|
+
* a TOC pane usually is. Headings carry the matching `scroll-margin-top`
|
|
223
|
+
* for the far commoner case where the document scrolls. */
|
|
224
|
+
scroll-padding-top: var(--wave-docs-scroll-padding);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* No `scroll-behavior: smooth` anywhere, and not for the usual reason.
|
|
229
|
+
* Next 16 only suppresses smooth scrolling across a route change when
|
|
230
|
+
* `<html>` carries `data-scroll-behavior="smooth"` — an attribute only the
|
|
231
|
+
* host can set — so a package-level `html { scroll-behavior: smooth }`
|
|
232
|
+
* animates every navigation and makes dev builds warn about a stylesheet
|
|
233
|
+
* inside `node_modules`. Hosts that want it opt in on their own `<html>`,
|
|
234
|
+
* with that attribute, behind `prefers-reduced-motion: no-preference`.
|
|
235
|
+
*/
|
|
196
236
|
}
|
|
197
237
|
|
|
198
238
|
/* ---------------------------------------------------------------------------
|
|
@@ -239,7 +279,14 @@
|
|
|
239
279
|
* plain `:focus` for the same reason. */
|
|
240
280
|
.wave-docs-skip-link:focus {
|
|
241
281
|
transform: translateY(0);
|
|
242
|
-
|
|
282
|
+
/* Transparent rather than `none`, here and at every other ring in this
|
|
283
|
+
* file: forced-colors mode zeroes `box-shadow` and honours `outline`, so
|
|
284
|
+
* `outline: none` would strictly remove the UA indicator and put nothing
|
|
285
|
+
* back. A transparent outline is invisible normally and repainted in a
|
|
286
|
+
* system colour there. The explicit `forced-colors` block at the end of
|
|
287
|
+
* this file is the belt to this pair of braces. */
|
|
288
|
+
outline: 2px solid transparent;
|
|
289
|
+
outline-offset: 2px;
|
|
243
290
|
box-shadow: var(--wave-docs-ring);
|
|
244
291
|
}
|
|
245
292
|
|
|
@@ -259,6 +306,12 @@
|
|
|
259
306
|
color: var(--wave-docs-fg);
|
|
260
307
|
font-size: 1rem;
|
|
261
308
|
line-height: 1.75;
|
|
309
|
+
/* Inherited by every descendant, which is the point: a sha256 digest in a
|
|
310
|
+
* paragraph, a long word in a heading or a list item each pushed the page
|
|
311
|
+
* to a 553-784px scrollWidth at the 320px viewport WCAG 1.4.10 Reflow
|
|
312
|
+
* tests at. `break-word` and not `word-break: break-all`, which would also
|
|
313
|
+
* chop ordinary prose mid-syllable. */
|
|
314
|
+
overflow-wrap: break-word;
|
|
262
315
|
/* No `max-width` on purpose — the docs shell owns column width. */
|
|
263
316
|
}
|
|
264
317
|
|
|
@@ -272,7 +325,7 @@
|
|
|
272
325
|
font-weight: 650;
|
|
273
326
|
line-height: 1.25;
|
|
274
327
|
text-wrap: balance;
|
|
275
|
-
scroll-margin-top: var(--wave-docs-
|
|
328
|
+
scroll-margin-top: var(--wave-docs-scroll-padding);
|
|
276
329
|
}
|
|
277
330
|
|
|
278
331
|
.wave-docs-prose > :is(h2, h3, h4, h5, h6) {
|
|
@@ -306,6 +359,12 @@
|
|
|
306
359
|
text-underline-offset: 0.2em;
|
|
307
360
|
text-decoration-thickness: 1px;
|
|
308
361
|
border-radius: 2px;
|
|
362
|
+
/* `anywhere`, not `break-word`: a link label is often one unbreakable
|
|
363
|
+
* token (`/api/v1/some-long-path`), and `break-word` only breaks a word
|
|
364
|
+
* that would overflow a line of its own. A bare autolinked URL already
|
|
365
|
+
* breaks after `/` under UAX#14 without help — which is why no
|
|
366
|
+
* `word-break` appears anywhere in this file. */
|
|
367
|
+
overflow-wrap: anywhere;
|
|
309
368
|
}
|
|
310
369
|
|
|
311
370
|
.wave-docs-prose a:hover {
|
|
@@ -317,7 +376,8 @@
|
|
|
317
376
|
.wave-docs-sidebar :is(a, button):focus-visible,
|
|
318
377
|
.wave-docs-toc a:focus-visible,
|
|
319
378
|
.wave-docs-youtube__facade:focus-visible {
|
|
320
|
-
outline:
|
|
379
|
+
outline: 2px solid transparent;
|
|
380
|
+
outline-offset: 2px;
|
|
321
381
|
box-shadow: var(--wave-docs-ring);
|
|
322
382
|
}
|
|
323
383
|
|
|
@@ -409,7 +469,8 @@
|
|
|
409
469
|
}
|
|
410
470
|
|
|
411
471
|
.wave-docs-table-scroll:focus-visible {
|
|
412
|
-
outline:
|
|
472
|
+
outline: 2px solid transparent;
|
|
473
|
+
outline-offset: 2px;
|
|
413
474
|
box-shadow: var(--wave-docs-ring);
|
|
414
475
|
}
|
|
415
476
|
|
|
@@ -475,50 +536,69 @@
|
|
|
475
536
|
/* ---------------------------------------------------------------------------
|
|
476
537
|
* Shiki
|
|
477
538
|
*
|
|
478
|
-
* Dual-theme promotion
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
539
|
+
* Dual-theme promotion, driven entirely by custom properties. `render.ts`
|
|
540
|
+
* passes `defaultColor: false`, so Shiki writes NO inline `color` at all — each
|
|
541
|
+
* token span carries `--shiki-light` and `--shiki-dark` and nothing else, and
|
|
542
|
+
* picking a theme is an ordinary cascade problem solved by an ordinary
|
|
543
|
+
* selector. No `!important` is involved, and none may be reintroduced: it would
|
|
544
|
+
* outrank the consumer's own overrides for no benefit.
|
|
545
|
+
*
|
|
546
|
+
* Scoped to `.wave-docs-prose`, and inside `@layer components` like everything
|
|
547
|
+
* else here. A bare, unlayered `.shiki` beat every layer on any page that also
|
|
548
|
+
* renders Shiki output from another package, silently repainting it.
|
|
483
549
|
* ------------------------------------------------------------------------ */
|
|
484
550
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
551
|
+
@layer components {
|
|
552
|
+
.wave-docs-prose .shiki {
|
|
553
|
+
padding: 1rem 1.125rem;
|
|
554
|
+
border: 1px solid var(--wave-docs-border);
|
|
555
|
+
border-radius: var(--wave-docs-radius);
|
|
556
|
+
/* Our token, not `--shiki-light-bg`: the code block is part of the page's
|
|
557
|
+
* surface ramp, and a theme's own background is whatever GitHub chose. */
|
|
558
|
+
background-color: var(--wave-docs-code-block-bg);
|
|
559
|
+
overflow-x: auto;
|
|
560
|
+
font-family: var(--wave-docs-font-mono);
|
|
561
|
+
font-size: 0.875rem;
|
|
562
|
+
line-height: 1.7;
|
|
563
|
+
tab-size: 2;
|
|
564
|
+
}
|
|
496
565
|
|
|
497
|
-
.shiki
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
566
|
+
.wave-docs-prose .shiki,
|
|
567
|
+
.wave-docs-prose .shiki span {
|
|
568
|
+
color: var(--shiki-light);
|
|
569
|
+
}
|
|
501
570
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
571
|
+
/* Shiki puts `tabindex="0"` on the `<pre>` so a keyboard reader can scroll a
|
|
572
|
+
* wide block — which makes it the one focusable element in this package that
|
|
573
|
+
* had no focus style. */
|
|
574
|
+
.wave-docs-prose .shiki:focus-visible {
|
|
575
|
+
outline: 2px solid transparent;
|
|
576
|
+
outline-offset: 2px;
|
|
577
|
+
box-shadow: var(--wave-docs-ring);
|
|
578
|
+
}
|
|
505
579
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
color: var(--shiki-dark) !important;
|
|
580
|
+
.wave-docs-prose .shiki code {
|
|
581
|
+
display: grid;
|
|
582
|
+
font-family: inherit;
|
|
510
583
|
}
|
|
511
|
-
|
|
512
|
-
|
|
584
|
+
|
|
585
|
+
.wave-docs-prose .shiki .line {
|
|
586
|
+
min-height: 1lh;
|
|
513
587
|
}
|
|
514
|
-
}
|
|
515
588
|
|
|
516
|
-
:
|
|
517
|
-
:root[data-theme='
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
589
|
+
@media (prefers-color-scheme: dark) {
|
|
590
|
+
:root[data-theme='system'] .wave-docs-prose .shiki,
|
|
591
|
+
:root[data-theme='system'] .wave-docs-prose .shiki span {
|
|
592
|
+
color: var(--shiki-dark);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
:root[data-theme='dark'] .wave-docs-prose .shiki,
|
|
597
|
+
:root[data-theme='dark'] .wave-docs-prose .shiki span,
|
|
598
|
+
:root.dark .wave-docs-prose .shiki,
|
|
599
|
+
:root.dark .wave-docs-prose .shiki span {
|
|
600
|
+
color: var(--shiki-dark);
|
|
601
|
+
}
|
|
522
602
|
}
|
|
523
603
|
|
|
524
604
|
/* ---------------------------------------------------------------------------
|
|
@@ -851,7 +931,8 @@
|
|
|
851
931
|
.wave-docs-search-trigger:focus-visible,
|
|
852
932
|
.wave-docs-search-close:focus-visible,
|
|
853
933
|
.wave-docs-search-result-link:focus-visible {
|
|
854
|
-
outline:
|
|
934
|
+
outline: 2px solid transparent;
|
|
935
|
+
outline-offset: 2px;
|
|
855
936
|
box-shadow: var(--wave-docs-ring);
|
|
856
937
|
}
|
|
857
938
|
|
|
@@ -905,6 +986,22 @@
|
|
|
905
986
|
gap: 0.5rem;
|
|
906
987
|
padding: 0.75rem;
|
|
907
988
|
border-block-end: 1px solid var(--wave-docs-border);
|
|
989
|
+
/* Matches the dialog's corners, so the focus ring below traces them
|
|
990
|
+
* instead of poking out of the rounded frame. */
|
|
991
|
+
border-start-start-radius: var(--wave-docs-radius);
|
|
992
|
+
border-start-end-radius: var(--wave-docs-radius);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/* The ring belongs to the row, not the input. A 2px ring drawn around a
|
|
996
|
+
* borderless full-width input reads as an error state, and the previous
|
|
997
|
+
* answer — "the dialog frame is the indicator" — was a static 1.31:1 border
|
|
998
|
+
* that looks identical focused and unfocused (WCAG 2.4.7 wants a change of
|
|
999
|
+
* appearance, 1.4.11 wants 3:1; `accent` on `bg` is 5.16:1 / 7.50:1).
|
|
1000
|
+
* `outline-offset: -2px` draws it inward, because the dialog clips with
|
|
1001
|
+
* `overflow: hidden` and an outward ring would be sliced off. */
|
|
1002
|
+
.wave-docs-search-input-row:has(.wave-docs-search-input:focus-visible) {
|
|
1003
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
1004
|
+
outline-offset: -2px;
|
|
908
1005
|
}
|
|
909
1006
|
|
|
910
1007
|
.wave-docs-search-input {
|
|
@@ -918,9 +1015,10 @@
|
|
|
918
1015
|
font-size: 1rem;
|
|
919
1016
|
}
|
|
920
1017
|
|
|
1018
|
+
/* Not `outline: 2px solid transparent` like every other control here: the
|
|
1019
|
+
* row above already draws a real outline, and a second one on the input
|
|
1020
|
+
* would double up in forced-colors mode. */
|
|
921
1021
|
.wave-docs-search-input:focus {
|
|
922
|
-
/* The dialog frame is the focus indicator here; a ring on a full-width
|
|
923
|
-
* borderless input reads as an error state. */
|
|
924
1022
|
outline: none;
|
|
925
1023
|
}
|
|
926
1024
|
|
|
@@ -956,9 +1054,16 @@
|
|
|
956
1054
|
}
|
|
957
1055
|
|
|
958
1056
|
/* Driven by `aria-selected` through the active class the dialog sets — the
|
|
959
|
-
* combobox keeps focus in the input
|
|
1057
|
+
* combobox keeps focus in the input and every result is `tabindex="-1"`, so
|
|
1058
|
+
* `:focus-visible` can never fire here and the tint IS the whole indicator.
|
|
1059
|
+
* A tint alone is 1.12:1 light / 1.19:1 dark; WCAG 1.4.11 asks 3:1 of a
|
|
1060
|
+
* state indicator, so the outline carries it (`accent` on `accent-subtle` is
|
|
1061
|
+
* 4.60:1 / 6.30:1) and the tint is left to do the soft work. Inset by 2px so
|
|
1062
|
+
* it stays inside the scrolling results list. */
|
|
960
1063
|
.wave-docs-search-result-active {
|
|
961
1064
|
background: var(--wave-docs-accent-subtle);
|
|
1065
|
+
outline: 2px solid var(--wave-docs-accent);
|
|
1066
|
+
outline-offset: -2px;
|
|
962
1067
|
}
|
|
963
1068
|
|
|
964
1069
|
.wave-docs-search-result-link {
|
|
@@ -1031,3 +1136,44 @@
|
|
|
1031
1136
|
white-space: nowrap;
|
|
1032
1137
|
}
|
|
1033
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
/* ---------------------------------------------------------------------------
|
|
1141
|
+
* Forced colours
|
|
1142
|
+
*
|
|
1143
|
+
* Windows High Contrast and its kin drop `box-shadow` entirely and replace
|
|
1144
|
+
* every colour with one from the user's palette. Every focus indicator in this
|
|
1145
|
+
* package is a `box-shadow` ring, so without this block the package would
|
|
1146
|
+
* *remove* the UA outline from every focusable surface it styles and draw
|
|
1147
|
+
* nothing in its place — a strictly worse page than shipping no CSS at all.
|
|
1148
|
+
*
|
|
1149
|
+
* `Highlight` rather than a token: user-chosen system colours are the whole
|
|
1150
|
+
* point of the mode, and `--wave-docs-accent` is forced to something else
|
|
1151
|
+
* anyway. This list must stay in step with every `:focus-visible` rule above;
|
|
1152
|
+
* `src/styles.test.ts` fails if one appears without its entry here.
|
|
1153
|
+
* ------------------------------------------------------------------------ */
|
|
1154
|
+
|
|
1155
|
+
@layer components {
|
|
1156
|
+
@media (forced-colors: active) {
|
|
1157
|
+
.wave-docs-skip-link:focus,
|
|
1158
|
+
.wave-docs-prose a:focus-visible,
|
|
1159
|
+
.wave-docs-sidebar :is(a, button):focus-visible,
|
|
1160
|
+
.wave-docs-toc a:focus-visible,
|
|
1161
|
+
.wave-docs-youtube__facade:focus-visible,
|
|
1162
|
+
.wave-docs-table-scroll:focus-visible,
|
|
1163
|
+
.wave-docs-prose .shiki:focus-visible,
|
|
1164
|
+
.wave-docs-search-trigger:focus-visible,
|
|
1165
|
+
.wave-docs-search-close:focus-visible,
|
|
1166
|
+
.wave-docs-search-result-link:focus-visible {
|
|
1167
|
+
outline: 3px solid Highlight;
|
|
1168
|
+
outline-offset: 2px;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/* Inward, like their normal-mode rules: one is clipped by the dialog, the
|
|
1172
|
+
* other by the scrolling results list. */
|
|
1173
|
+
.wave-docs-search-input-row:has(.wave-docs-search-input:focus-visible),
|
|
1174
|
+
.wave-docs-search-result-active {
|
|
1175
|
+
outline: 2px solid Highlight;
|
|
1176
|
+
outline-offset: -2px;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,17 +7,24 @@ import { Root } from "hast";
|
|
|
7
7
|
* Consumers extend this with their own schema — {@link DocsConfig.frontmatterSchema} —
|
|
8
8
|
* and the extra fields flow through the generic parameter on {@link DocFile}
|
|
9
9
|
* and friends rather than widening this interface.
|
|
10
|
+
*
|
|
11
|
+
* The optionals are spelled `?: T | undefined` because a schema written with
|
|
12
|
+
* `.optional()` instead of `.exactOptional()` infers exactly that shape, and
|
|
13
|
+
* under `exactOptionalPropertyTypes` the narrow spelling rejects it — as a
|
|
14
|
+
* nine-line error through Standard Schema's internals at the config, plus a
|
|
15
|
+
* `Property 'audience' does not exist on type 'DocFrontmatter'` at every read
|
|
16
|
+
* site, because the inference then collapses to this default.
|
|
10
17
|
*/
|
|
11
18
|
interface DocFrontmatter {
|
|
12
19
|
/** Page title. Used for `<h1>` fallbacks, `<title>`, and search. */
|
|
13
20
|
title: string;
|
|
14
21
|
/** One-line summary. Used for `<meta name="description">` and search. */
|
|
15
|
-
description?: string;
|
|
22
|
+
description?: string | undefined;
|
|
16
23
|
/**
|
|
17
24
|
* Sidebar label, when it should differ from {@link DocFrontmatter.title}.
|
|
18
25
|
* Sidebars are narrow; page ancestors are not.
|
|
19
26
|
*/
|
|
20
|
-
label?: string;
|
|
27
|
+
label?: string | undefined;
|
|
21
28
|
/**
|
|
22
29
|
* Excluded from navigation, search and `generateStaticParams`.
|
|
23
30
|
*
|
|
@@ -25,18 +32,18 @@ interface DocFrontmatter {
|
|
|
25
32
|
* production builds, so branching on it would hide drafts in exactly the
|
|
26
33
|
* place reviewers look. Gate on an explicit config flag instead.
|
|
27
34
|
*/
|
|
28
|
-
draft?: boolean;
|
|
35
|
+
draft?: boolean | undefined;
|
|
29
36
|
/**
|
|
30
37
|
* Previous URLs for this page, relative to the docs base path
|
|
31
38
|
* (e.g. `['old-name', 'legacy/old-name']`). The Next adapter turns these
|
|
32
39
|
* into permanent redirects so a rename never becomes a silent 404.
|
|
33
40
|
*/
|
|
34
|
-
aliases?: string[];
|
|
41
|
+
aliases?: string[] | undefined;
|
|
35
42
|
/**
|
|
36
43
|
* Sort weight within its directory, for directories without a `meta.json`.
|
|
37
44
|
* Lower sorts first; pages without an order sort last, alphabetically.
|
|
38
45
|
*/
|
|
39
|
-
order?: number;
|
|
46
|
+
order?: number | undefined;
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* A single documentation page discovered on disk.
|
|
@@ -75,7 +82,7 @@ interface DocNavPage {
|
|
|
75
82
|
interface DocNavGroup {
|
|
76
83
|
type: 'group';
|
|
77
84
|
title: string;
|
|
78
|
-
href?: string;
|
|
85
|
+
href?: string | undefined;
|
|
79
86
|
children: DocNavNode[];
|
|
80
87
|
}
|
|
81
88
|
/** A non-interactive heading between groups, from `"---Label---"` in meta.json. */
|
|
@@ -108,12 +115,12 @@ type DocNavNode = DocNavPage | DocNavGroup | DocNavSeparator | DocNavLink;
|
|
|
108
115
|
*/
|
|
109
116
|
interface DocsMeta {
|
|
110
117
|
/** Directory title, shown as the group heading. Defaults to the dirname. */
|
|
111
|
-
title?: string;
|
|
118
|
+
title?: string | undefined;
|
|
112
119
|
/** Ordered entries. Omit to sort by frontmatter `order`, then alphabetically. */
|
|
113
120
|
pages?: Array<string | {
|
|
114
121
|
title: string;
|
|
115
122
|
href: string;
|
|
116
|
-
}
|
|
123
|
+
}> | undefined;
|
|
117
124
|
}
|
|
118
125
|
/**
|
|
119
126
|
* A heading captured from the rendered tree.
|
|
@@ -244,12 +251,12 @@ href: string, from: DocLinkContext) => string | undefined;
|
|
|
244
251
|
*/
|
|
245
252
|
type ImageResolver = (src: string, from: DocLinkContext) => Promise<{
|
|
246
253
|
src: string;
|
|
247
|
-
width?: number;
|
|
248
|
-
height?: number;
|
|
254
|
+
width?: number | undefined;
|
|
255
|
+
height?: number | undefined;
|
|
249
256
|
} | undefined> | {
|
|
250
257
|
src: string;
|
|
251
|
-
width?: number;
|
|
252
|
-
height?: number;
|
|
258
|
+
width?: number | undefined;
|
|
259
|
+
height?: number | undefined;
|
|
253
260
|
} | undefined;
|
|
254
261
|
/**
|
|
255
262
|
* How a documentation tree is read.
|
|
@@ -267,18 +274,18 @@ interface DocsConfig<TFrontmatter extends DocFrontmatter = DocFrontmatter> {
|
|
|
267
274
|
*/
|
|
268
275
|
contentDir: string;
|
|
269
276
|
/** URL prefix the docs are mounted at. Defaults to `'/docs'`. */
|
|
270
|
-
basePath?: string;
|
|
277
|
+
basePath?: string | undefined;
|
|
271
278
|
/**
|
|
272
279
|
* Include pages marked `draft: true`. Defaults to `false`.
|
|
273
280
|
*
|
|
274
281
|
* Drive this from your own env check — deliberately not `NODE_ENV`.
|
|
275
282
|
*/
|
|
276
|
-
includeDrafts?: boolean;
|
|
283
|
+
includeDrafts?: boolean | undefined;
|
|
277
284
|
/**
|
|
278
285
|
* Fail the build when an internal link resolves to a page that does not
|
|
279
286
|
* exist. Defaults to `true`; there is no good reason to turn it off.
|
|
280
287
|
*/
|
|
281
|
-
assertLinks?: boolean;
|
|
288
|
+
assertLinks?: boolean | undefined;
|
|
282
289
|
/**
|
|
283
290
|
* Validates every page's frontmatter. Defaults to `docFrontmatterSchema`
|
|
284
291
|
* from `@waveso/docs/frontmatter`.
|
|
@@ -301,21 +308,28 @@ interface DocsConfig<TFrontmatter extends DocFrontmatter = DocFrontmatter> {
|
|
|
301
308
|
*
|
|
302
309
|
* Three things are worth knowing before you write one:
|
|
303
310
|
*
|
|
304
|
-
* - **The
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
311
|
+
* - **The package's fields are not yours to drop.** `title` drives the
|
|
312
|
+
* `<h1>` fallback and `<title>`, `draft` the visibility filter, `aliases`
|
|
313
|
+
* the redirects, `order`/`label` the sidebar — so all six are re-read from
|
|
314
|
+
* the YAML by `docFrontmatterSchema` and laid back over your output. A
|
|
315
|
+
* bare `z.object({ title, audience })` therefore cannot publish a draft or
|
|
316
|
+
* lose a redirect; it only costs you the six in the inferred type. Nothing
|
|
317
|
+
* in the type system could have caught that: `TFrontmatter extends
|
|
318
|
+
* DocFrontmatter` constrains `title` and nothing else, because the other
|
|
319
|
+
* five are optional. The price is that a `.default()`, `.transform()` or
|
|
320
|
+
* `.coerce` aimed at one of the six is not honoured — the YAML wins.
|
|
308
321
|
* - **Unknown keys are stripped, by every validator worth using.** The
|
|
309
|
-
* parsed frontmatter is exactly what the schema declares, so
|
|
310
|
-
* field you intend to read — extending
|
|
311
|
-
* `docFrontmatterSchema` is the shortest way to
|
|
322
|
+
* parsed frontmatter is exactly what the schema declares plus the six, so
|
|
323
|
+
* declare every field you intend to read — extending
|
|
324
|
+
* `docFrontmatterSchema` is the shortest way to get the built-ins back in
|
|
325
|
+
* the type as well.
|
|
312
326
|
* - **Identity is load-bearing.** The filesystem scan is memoised per
|
|
313
327
|
* resolved config, and two schema objects are only "the same schema" when
|
|
314
328
|
* they are the same object. Export one from a shared module (as above)
|
|
315
329
|
* rather than building it inline in each route file, or each file pays for
|
|
316
330
|
* its own scan.
|
|
317
331
|
*/
|
|
318
|
-
frontmatterSchema?: StandardSchemaV1<unknown, TFrontmatter
|
|
332
|
+
frontmatterSchema?: StandardSchemaV1<unknown, TFrontmatter> | undefined;
|
|
319
333
|
}
|
|
320
334
|
/** {@link DocsConfig} with defaults applied. */
|
|
321
335
|
interface ResolvedDocsConfig<TFrontmatter extends DocFrontmatter = DocFrontmatter> {
|
|
@@ -324,11 +338,11 @@ interface ResolvedDocsConfig<TFrontmatter extends DocFrontmatter = DocFrontmatte
|
|
|
324
338
|
includeDrafts: boolean;
|
|
325
339
|
assertLinks: boolean;
|
|
326
340
|
/**
|
|
327
|
-
* As supplied.
|
|
328
|
-
* `
|
|
329
|
-
*
|
|
341
|
+
* As supplied. `resolveDocsConfig` omits the key rather than setting it to
|
|
342
|
+
* `undefined` when the built-in `docFrontmatterSchema` applies, so the
|
|
343
|
+
* default lives in exactly one place: `parseFrontmatter`.
|
|
330
344
|
*/
|
|
331
|
-
frontmatterSchema?: StandardSchemaV1<unknown, TFrontmatter
|
|
345
|
+
frontmatterSchema?: StandardSchemaV1<unknown, TFrontmatter> | undefined;
|
|
332
346
|
}
|
|
333
347
|
//#endregion
|
|
334
348
|
export { DocFile, DocFrontmatter, DocLinkContext, DocNavGroup, DocNavLink, DocNavNode, DocNavPage, DocNavSeparator, DocsConfig, DocsMeta, ImageResolver, LinkResolver, RenderedDoc, ResolvedDocsConfig, SearchRecord, TocEntry };
|