react-cheminfo 0.37.0 → 0.38.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 +77 -1
- package/bin/check-seo.mjs +43 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +9 -1
- package/lib/locales/en.json +9 -1
- package/lib/locales/es.json +9 -1
- package/lib/locales/fr.json +9 -1
- package/lib/locales/it.json +9 -1
- package/lib/number/core/index.d.ts +1 -1
- package/lib/number/core/index.d.ts.map +1 -1
- package/lib/number/core/index.js +1 -1
- package/lib/number/core/index.js.map +1 -1
- package/lib/number/core/numberText.d.ts +13 -0
- package/lib/number/core/numberText.d.ts.map +1 -1
- package/lib/number/core/numberText.js +20 -0
- package/lib/number/core/numberText.js.map +1 -1
- package/lib/range/core/index.d.ts +4 -0
- package/lib/range/core/index.d.ts.map +1 -0
- package/lib/range/core/index.js +3 -0
- package/lib/range/core/index.js.map +1 -0
- package/lib/range/core/rangeBounds.d.ts +79 -0
- package/lib/range/core/rangeBounds.d.ts.map +1 -0
- package/lib/range/core/rangeBounds.js +110 -0
- package/lib/range/core/rangeBounds.js.map +1 -0
- package/lib/range/core/rangeHistogram.d.ts +13 -0
- package/lib/range/core/rangeHistogram.d.ts.map +1 -0
- package/lib/range/core/rangeHistogram.js +39 -0
- package/lib/range/core/rangeHistogram.js.map +1 -0
- package/lib/range/ui/RangeHistogram.d.ts +20 -0
- package/lib/range/ui/RangeHistogram.d.ts.map +1 -0
- package/lib/range/ui/RangeHistogram.js +18 -0
- package/lib/range/ui/RangeHistogram.js.map +1 -0
- package/lib/range/ui/RangeSlider.d.ts +77 -0
- package/lib/range/ui/RangeSlider.d.ts.map +1 -0
- package/lib/range/ui/RangeSlider.js +60 -0
- package/lib/range/ui/RangeSlider.js.map +1 -0
- package/lib/range/ui/RangeValue.d.ts +41 -0
- package/lib/range/ui/RangeValue.d.ts.map +1 -0
- package/lib/range/ui/RangeValue.js +73 -0
- package/lib/range/ui/RangeValue.js.map +1 -0
- package/lib/range/ui/index.d.ts +3 -0
- package/lib/range/ui/index.d.ts.map +1 -0
- package/lib/range/ui/index.js +2 -0
- package/lib/range/ui/index.js.map +1 -0
- package/lib/seo/core/documentMeta.d.ts +16 -0
- package/lib/seo/core/documentMeta.d.ts.map +1 -1
- package/lib/seo/core/documentMeta.js +18 -0
- package/lib/seo/core/documentMeta.js.map +1 -1
- package/lib/seo/core/index.d.ts +3 -1
- package/lib/seo/core/index.d.ts.map +1 -1
- package/lib/seo/core/index.js +2 -1
- package/lib/seo/core/index.js.map +1 -1
- package/lib/seo/core/noscript.d.ts +8 -0
- package/lib/seo/core/noscript.d.ts.map +1 -1
- package/lib/seo/core/noscript.js +10 -3
- package/lib/seo/core/noscript.js.map +1 -1
- package/lib/seo/core/pageMeta.d.ts +10 -2
- package/lib/seo/core/pageMeta.d.ts.map +1 -1
- package/lib/seo/core/pageMeta.js +12 -3
- package/lib/seo/core/pageMeta.js.map +1 -1
- package/lib/seo/core/siteFiles.d.ts +32 -3
- package/lib/seo/core/siteFiles.d.ts.map +1 -1
- package/lib/seo/core/siteFiles.js +13 -4
- package/lib/seo/core/siteFiles.js.map +1 -1
- package/lib/seo/core/startDocumentMeta.js +8 -7
- package/lib/seo/core/startDocumentMeta.js.map +1 -1
- package/lib/seo/vite/prerender.d.ts +26 -4
- package/lib/seo/vite/prerender.d.ts.map +1 -1
- package/lib/seo/vite/prerender.js +100 -30
- package/lib/seo/vite/prerender.js.map +1 -1
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/core.ts +1 -0
- package/src/locales/de.json +9 -1
- package/src/locales/en.json +9 -1
- package/src/locales/es.json +9 -1
- package/src/locales/fr.json +9 -1
- package/src/locales/it.json +9 -1
- package/src/number/core/index.ts +1 -0
- package/src/number/core/numberText.ts +20 -0
- package/src/range/core/index.ts +11 -0
- package/src/range/core/rangeBounds.ts +143 -0
- package/src/range/core/rangeHistogram.ts +38 -0
- package/src/range/ui/RangeHistogram.tsx +49 -0
- package/src/range/ui/RangeSlider.tsx +209 -0
- package/src/range/ui/RangeValue.tsx +166 -0
- package/src/range/ui/index.ts +2 -0
- package/src/seo/core/documentMeta.ts +22 -0
- package/src/seo/core/index.ts +8 -1
- package/src/seo/core/noscript.ts +20 -2
- package/src/seo/core/pageMeta.ts +29 -5
- package/src/seo/core/siteFiles.ts +45 -10
- package/src/seo/core/startDocumentMeta.ts +9 -9
- package/src/seo/vite/prerender.ts +160 -36
- package/src/ui.ts +1 -0
- package/styles/chrome.css +135 -0
|
@@ -23,6 +23,10 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
23
23
|
import type { Logger, Plugin } from 'vite';
|
|
24
24
|
|
|
25
25
|
import type { SiteId, SiteRecord } from '../../ecosystem/core/sites.ts';
|
|
26
|
+
import { CHROME_CATALOG } from '../../i18n/core/chromeCatalog.ts';
|
|
27
|
+
import type { Language } from '../../i18n/core/languages.ts';
|
|
28
|
+
import { DEFAULT_LANGUAGE } from '../../i18n/core/languages.ts';
|
|
29
|
+
import { withLanguagePath } from '../../language/core/languagePath.ts';
|
|
26
30
|
import { trimTrailingSlash } from '../../router/core/address.ts';
|
|
27
31
|
import type { NoscriptText } from '../core/noscript.ts';
|
|
28
32
|
import { noscriptIndex } from '../core/noscript.ts';
|
|
@@ -40,8 +44,17 @@ import { PAGE_BODY_MARKER, PAGE_HEAD_MARKER, fill } from '../core/template.ts';
|
|
|
40
44
|
export interface PrerenderOptions {
|
|
41
45
|
/** The site, named or passed. */
|
|
42
46
|
site: SiteRecord | SiteId;
|
|
43
|
-
/**
|
|
44
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Every address it answers, each with its title and description.
|
|
49
|
+
*
|
|
50
|
+
* A translated site passes a function instead: the build calls it once per
|
|
51
|
+
* language and writes that language's table, so the title and the description
|
|
52
|
+
* a search result shows are in the language of the page it points at. The
|
|
53
|
+
* paths are the same in every language — an id is never translated, or a
|
|
54
|
+
* progress key, a share link and a bookmark would all break on a language
|
|
55
|
+
* switch.
|
|
56
|
+
*/
|
|
57
|
+
routes: readonly RouteMeta[] | ((language: Language) => readonly RouteMeta[]);
|
|
45
58
|
/**
|
|
46
59
|
* Where the site is served, mount path included. Every absolute address is
|
|
47
60
|
* built on it, and `robots.txt` and the `noscript` index write their paths
|
|
@@ -91,7 +104,8 @@ export interface PrerenderOptions {
|
|
|
91
104
|
* `<!--cheminfo:body-->` at all.
|
|
92
105
|
* @default true
|
|
93
106
|
*/
|
|
94
|
-
noscript?:
|
|
107
|
+
noscript?:
|
|
108
|
+
boolean | NoscriptText | ((language: Language) => boolean | NoscriptText);
|
|
95
109
|
/**
|
|
96
110
|
* What each page says for itself, above the crawl path: its own heading, its
|
|
97
111
|
* prose and the facts it would show anyway, read from the same data the app
|
|
@@ -103,7 +117,19 @@ export interface PrerenderOptions {
|
|
|
103
117
|
* the bundle the browser downloads: only the build ever calls it.
|
|
104
118
|
* @default undefined — every page carries the menu alone
|
|
105
119
|
*/
|
|
106
|
-
content?: (route: RouteMeta) => PageContent | undefined;
|
|
120
|
+
content?: (route: RouteMeta, language: Language) => PageContent | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Every language the site is written in, the default one included.
|
|
123
|
+
*
|
|
124
|
+
* The build then writes one file per address per language — `/tutorial` and
|
|
125
|
+
* `/fr/tutorial` — each with its own `lang`, title, description and
|
|
126
|
+
* canonical, the `hreflang` set tying them together, and every variant in the
|
|
127
|
+
* sitemap. That is the whole of what makes a translation findable: a site
|
|
128
|
+
* keeping its language in a parameter or in storage serves five languages at
|
|
129
|
+
* one address, and a crawler indexes one.
|
|
130
|
+
* @default [the default language]
|
|
131
|
+
*/
|
|
132
|
+
languages?: readonly Language[];
|
|
107
133
|
}
|
|
108
134
|
|
|
109
135
|
/**
|
|
@@ -114,21 +140,43 @@ export interface PrerenderOptions {
|
|
|
114
140
|
* is not a path, or when the origin is not an absolute address.
|
|
115
141
|
*/
|
|
116
142
|
export function cheminfoPrerender(options: PrerenderOptions): Plugin {
|
|
117
|
-
const { site,
|
|
143
|
+
const { site, origin, robots = [] } = options;
|
|
144
|
+
const languages = options.languages ?? [DEFAULT_LANGUAGE];
|
|
145
|
+
const tableFor = (language: Language): readonly RouteMeta[] =>
|
|
146
|
+
typeof options.routes === 'function'
|
|
147
|
+
? options.routes(language)
|
|
148
|
+
: options.routes;
|
|
149
|
+
|
|
150
|
+
// Every language answers the same addresses, so each table is checked and
|
|
151
|
+
// they are checked against each other: a translation that renames a path or
|
|
152
|
+
// drops a page writes a file the sitemap of another language names, and the
|
|
153
|
+
// `hreflang` set would point at an address that is not there.
|
|
154
|
+
const routes = tableFor(DEFAULT_LANGUAGE);
|
|
118
155
|
assertRoutes(routes);
|
|
119
|
-
const
|
|
156
|
+
for (const language of languages) {
|
|
157
|
+
if (language === DEFAULT_LANGUAGE) continue;
|
|
158
|
+
assertSamePaths(routes, tableFor(language), language);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const structuredData = structuredDataOf(options, routes);
|
|
120
162
|
|
|
121
163
|
let out = 'dist';
|
|
122
164
|
let serve = false;
|
|
123
165
|
let logger: Logger | null = null;
|
|
124
166
|
|
|
125
|
-
const page = (template: string, route: RouteMeta) => {
|
|
167
|
+
const page = (template: string, route: RouteMeta, language: Language) => {
|
|
126
168
|
const head = fill(
|
|
127
|
-
template,
|
|
169
|
+
withHtmlLang(template, language),
|
|
128
170
|
PAGE_HEAD_MARKER,
|
|
129
|
-
`${pageHeadTags({
|
|
171
|
+
`${pageHeadTags({
|
|
172
|
+
site,
|
|
173
|
+
routes: tableFor(language),
|
|
174
|
+
origin,
|
|
175
|
+
languages,
|
|
176
|
+
url: withLanguagePath(language, route.path),
|
|
177
|
+
})}${structuredData}`,
|
|
130
178
|
);
|
|
131
|
-
const crawlPath = crawlPathOf(options, route);
|
|
179
|
+
const crawlPath = crawlPathOf(options, route, language, tableFor(language));
|
|
132
180
|
return crawlPath === '' ? head : fill(head, PAGE_BODY_MARKER, crawlPath);
|
|
133
181
|
};
|
|
134
182
|
|
|
@@ -145,37 +193,60 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
|
|
|
145
193
|
// from the home route rather than shipped with its markers showing.
|
|
146
194
|
transformIndexHtml: {
|
|
147
195
|
order: 'post',
|
|
148
|
-
handler: (html: string) =>
|
|
196
|
+
handler: (html: string) =>
|
|
197
|
+
serve ? page(html, homeRoute(routes), DEFAULT_LANGUAGE) : html,
|
|
149
198
|
},
|
|
150
199
|
|
|
151
|
-
closeBundle() {
|
|
200
|
+
async closeBundle() {
|
|
152
201
|
if (serve) return;
|
|
202
|
+
// The crawl path names the family in the chrome's own words, and every
|
|
203
|
+
// language but the default arrives as a chunk of its own, so they are all
|
|
204
|
+
// fetched before a single file is written.
|
|
205
|
+
await Promise.all(
|
|
206
|
+
languages
|
|
207
|
+
.filter((language) => language !== DEFAULT_LANGUAGE)
|
|
208
|
+
.map((language) => CHROME_CATALOG.load(language)),
|
|
209
|
+
);
|
|
210
|
+
|
|
153
211
|
const template = readFileSync(join(out, 'index.html'), 'utf8');
|
|
154
212
|
|
|
155
|
-
const write = (route: RouteMeta, file: string) => {
|
|
213
|
+
const write = (route: RouteMeta, language: Language, file: string) => {
|
|
156
214
|
mkdirSync(dirname(file), { recursive: true });
|
|
157
|
-
writeFileSync(file, page(template, route));
|
|
215
|
+
writeFileSync(file, page(template, route, language));
|
|
158
216
|
};
|
|
159
217
|
|
|
160
|
-
let
|
|
161
|
-
for (const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
218
|
+
let written = 0;
|
|
219
|
+
for (const language of languages) {
|
|
220
|
+
const table = tableFor(language);
|
|
221
|
+
let root = false;
|
|
222
|
+
for (const route of table) {
|
|
223
|
+
const address = trimTrailingSlash(
|
|
224
|
+
withLanguagePath(language, route.path),
|
|
225
|
+
);
|
|
226
|
+
if (address === '/') root = true;
|
|
227
|
+
write(
|
|
228
|
+
route,
|
|
229
|
+
language,
|
|
230
|
+
address === '/'
|
|
231
|
+
? join(out, 'index.html')
|
|
232
|
+
: join(out, address.slice(1), 'index.html'),
|
|
233
|
+
);
|
|
234
|
+
written++;
|
|
235
|
+
}
|
|
236
|
+
// The file a static server hands out for the mount itself. A table
|
|
237
|
+
// naming no root would otherwise leave the template vite built, and
|
|
238
|
+
// ship a site whose front page carries its markers instead of a head.
|
|
239
|
+
// A language other than the default already has its own front page at
|
|
240
|
+
// `/<language>`, so only the unprefixed one is ever missing.
|
|
241
|
+
if (!root && language === DEFAULT_LANGUAGE) {
|
|
242
|
+
write(homeRoute(table), language, join(out, 'index.html'));
|
|
243
|
+
written++;
|
|
244
|
+
}
|
|
170
245
|
}
|
|
171
|
-
// The file a static server hands out for the mount itself. A table naming
|
|
172
|
-
// no root would otherwise leave the template vite built, and ship a site
|
|
173
|
-
// whose front page carries its markers instead of a head.
|
|
174
|
-
if (!root) write(homeRoute(routes), join(out, 'index.html'));
|
|
175
246
|
|
|
176
247
|
writeFileSync(
|
|
177
248
|
join(out, 'sitemap.xml'),
|
|
178
|
-
sitemapXml({ site, routes, origin }),
|
|
249
|
+
sitemapXml({ site, routes, origin, languages }),
|
|
179
250
|
);
|
|
180
251
|
if (robots !== false) {
|
|
181
252
|
writeFileSync(
|
|
@@ -184,17 +255,22 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
|
|
|
184
255
|
);
|
|
185
256
|
}
|
|
186
257
|
|
|
258
|
+
const spoken =
|
|
259
|
+
languages.length > 1 ? ` in ${languages.length} languages` : '';
|
|
187
260
|
logger?.info(
|
|
188
|
-
`${
|
|
261
|
+
`${written} pages prerendered${spoken}, and listed in sitemap.xml`,
|
|
189
262
|
);
|
|
190
263
|
},
|
|
191
264
|
};
|
|
192
265
|
}
|
|
193
266
|
|
|
194
|
-
function structuredDataOf(
|
|
267
|
+
function structuredDataOf(
|
|
268
|
+
options: PrerenderOptions,
|
|
269
|
+
routes: readonly RouteMeta[],
|
|
270
|
+
): string {
|
|
195
271
|
const { category, ...rest } = options;
|
|
196
272
|
if (category === false) return '';
|
|
197
|
-
const { site,
|
|
273
|
+
const { site, origin } = rest;
|
|
198
274
|
const { operatingSystem, description, browserRequirements, currency } = rest;
|
|
199
275
|
return `\n${structuredDataScript({
|
|
200
276
|
site,
|
|
@@ -208,16 +284,64 @@ function structuredDataOf(options: PrerenderOptions): string {
|
|
|
208
284
|
})}`;
|
|
209
285
|
}
|
|
210
286
|
|
|
211
|
-
function crawlPathOf(
|
|
212
|
-
|
|
287
|
+
function crawlPathOf(
|
|
288
|
+
options: PrerenderOptions,
|
|
289
|
+
route: RouteMeta,
|
|
290
|
+
language: Language,
|
|
291
|
+
routes: readonly RouteMeta[],
|
|
292
|
+
): string {
|
|
293
|
+
const { site, origin } = options;
|
|
294
|
+
// The crawl path of a translated page is that language's: its heading, its
|
|
295
|
+
// intro and the label under every link. Declared as a function of the
|
|
296
|
+
// language, the site writes each of them from its own catalog; declared as a
|
|
297
|
+
// record, it is the same words on every language, which is what a site that
|
|
298
|
+
// speaks one wants and is what it used to be.
|
|
299
|
+
const declared = options.noscript ?? true;
|
|
300
|
+
const noscript =
|
|
301
|
+
typeof declared === 'function' ? declared(language) : declared;
|
|
213
302
|
if (noscript === false) return '';
|
|
214
|
-
const content = options.content?.(route);
|
|
303
|
+
const content = options.content?.(route, language);
|
|
215
304
|
const { routes: listed, ...prose } = noscript === true ? {} : noscript;
|
|
305
|
+
// The crawl path of a translated page links to that language's addresses: a
|
|
306
|
+
// French page listing the English ones is a crawler's only route out of it,
|
|
307
|
+
// and it would lead straight back out of the translation.
|
|
308
|
+
const menu = (listed ?? routes).map((entry) => ({
|
|
309
|
+
...entry,
|
|
310
|
+
path: withLanguagePath(language, entry.path),
|
|
311
|
+
}));
|
|
216
312
|
return noscriptIndex({
|
|
217
313
|
site,
|
|
218
314
|
origin,
|
|
219
315
|
...prose,
|
|
220
|
-
routes:
|
|
316
|
+
routes: menu,
|
|
221
317
|
content,
|
|
318
|
+
language,
|
|
222
319
|
});
|
|
223
320
|
}
|
|
321
|
+
|
|
322
|
+
// Two tables of one site answer the same addresses. A translated table that
|
|
323
|
+
// renames or drops a path leaves the `hreflang` set of every other language
|
|
324
|
+
// pointing at an address nothing wrote, which is a soft 404 on each of them.
|
|
325
|
+
function assertSamePaths(
|
|
326
|
+
source: readonly RouteMeta[],
|
|
327
|
+
translated: readonly RouteMeta[],
|
|
328
|
+
language: Language,
|
|
329
|
+
): void {
|
|
330
|
+
const wanted = source.map((route) => route.path).join(' ');
|
|
331
|
+
const written = translated.map((route) => route.path).join(' ');
|
|
332
|
+
if (wanted !== written) {
|
|
333
|
+
throw new Error(
|
|
334
|
+
`the ${language} route table answers different addresses than the source one: an address is the same in every language`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// The template is written in the default language, so each file says which
|
|
340
|
+
// language it is actually in. A page whose `lang` lies is read aloud wrong and
|
|
341
|
+
// offered to the wrong reader.
|
|
342
|
+
function withHtmlLang(template: string, language: Language): string {
|
|
343
|
+
return template.replace(
|
|
344
|
+
/<html(?<attributes>[^>]*)\slang="[^"]*"/i,
|
|
345
|
+
`<html$<attributes> lang="${language}"`,
|
|
346
|
+
);
|
|
347
|
+
}
|
package/src/ui.ts
CHANGED
|
@@ -24,6 +24,7 @@ export * from './parallel/ui/index.ts';
|
|
|
24
24
|
export * from './pedagogy/ui/index.ts';
|
|
25
25
|
export * from './periodic/ui/index.ts';
|
|
26
26
|
export * from './projection/ui/index.ts';
|
|
27
|
+
export * from './range/ui/index.ts';
|
|
27
28
|
export * from './router/ui/index.ts';
|
|
28
29
|
export * from './scatter/ui/index.ts';
|
|
29
30
|
export * from './scatter3d/ui/index.ts';
|
package/styles/chrome.css
CHANGED
|
@@ -570,6 +570,141 @@ select,
|
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
+
/* ── Range slider ────────────────────────────────────────────────────────── */
|
|
574
|
+
|
|
575
|
+
.range-slider {
|
|
576
|
+
display: flex;
|
|
577
|
+
min-width: 0;
|
|
578
|
+
flex-direction: column;
|
|
579
|
+
gap: 0.2rem;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/* As tall as the clear button, so the row does not jump when it appears. */
|
|
583
|
+
.range-slider__head {
|
|
584
|
+
display: flex;
|
|
585
|
+
min-height: 24px;
|
|
586
|
+
align-items: center;
|
|
587
|
+
justify-content: space-between;
|
|
588
|
+
gap: 0.5rem;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.range-slider__label {
|
|
592
|
+
color: var(--text);
|
|
593
|
+
font-size: 0.8125rem;
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.range-slider__unit {
|
|
598
|
+
color: var(--text-muted);
|
|
599
|
+
font-weight: 400;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/* A range's two handles stand half a handle past the track at either end, so
|
|
603
|
+
the track is inset by that much to keep them inside the box. */
|
|
604
|
+
.range-slider__track {
|
|
605
|
+
padding: 0 8px;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.range-slider__histogram {
|
|
609
|
+
display: block;
|
|
610
|
+
width: 100%;
|
|
611
|
+
height: 1.75rem;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.range-slider__bars {
|
|
615
|
+
fill: var(--border-strong);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.range-slider__bars--kept {
|
|
619
|
+
fill: color-mix(in oklab, var(--accent) 60%, var(--surface));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* Blueprint paints the track in its own grey and the kept part in its own
|
|
623
|
+
blue, which are the two colours the family never uses. */
|
|
624
|
+
.range-slider .bp6-slider {
|
|
625
|
+
min-width: 0;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.range-slider .bp6-slider-progress {
|
|
629
|
+
background: var(--border);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.range-slider .bp6-slider-progress.bp6-intent-primary {
|
|
633
|
+
background: var(--accent);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.range-slider .bp6-slider-handle {
|
|
637
|
+
background: var(--surface);
|
|
638
|
+
box-shadow:
|
|
639
|
+
0 0 0 1px var(--accent),
|
|
640
|
+
var(--shadow-sm);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.range-slider .bp6-slider-handle:hover {
|
|
644
|
+
background: color-mix(in oklab, var(--accent) 12%, var(--surface));
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.range-slider .bp6-slider-handle.bp6-active {
|
|
648
|
+
background: color-mix(in oklab, var(--accent) 28%, var(--surface));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.range-slider .bp6-slider-handle:focus-visible {
|
|
652
|
+
outline: 2px solid var(--accent);
|
|
653
|
+
outline-offset: 1px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.range-slider .bp6-disabled .bp6-slider-handle {
|
|
657
|
+
background: var(--surface-sunken);
|
|
658
|
+
box-shadow: 0 0 0 1px var(--border-strong);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.range-slider__values {
|
|
662
|
+
display: flex;
|
|
663
|
+
align-items: center;
|
|
664
|
+
justify-content: space-between;
|
|
665
|
+
gap: 0.5rem;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* A value reads as text and types like a box: the cursor says which. */
|
|
669
|
+
.range-slider__value {
|
|
670
|
+
padding: 0.1rem 0.35rem;
|
|
671
|
+
border: 0;
|
|
672
|
+
border-radius: 4px;
|
|
673
|
+
background: none;
|
|
674
|
+
color: var(--text);
|
|
675
|
+
cursor: text;
|
|
676
|
+
font-size: 0.8125rem;
|
|
677
|
+
font-variant-numeric: tabular-nums;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.range-slider__value:focus-visible {
|
|
681
|
+
outline: 2px solid var(--accent);
|
|
682
|
+
outline-offset: 1px;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.range-slider__value:disabled {
|
|
686
|
+
cursor: default;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* An open side shows the end of the track, faint: the scale without a bound. */
|
|
690
|
+
.range-slider__value--open {
|
|
691
|
+
color: var(--text-faint);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
@media (hover: hover) {
|
|
695
|
+
.range-slider__value:not(:disabled):hover {
|
|
696
|
+
background-color: color-mix(in oklab, var(--accent) 12%, transparent);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.range-slider__editor {
|
|
701
|
+
width: 6.5rem;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.range-slider__editor--max .bp6-input {
|
|
705
|
+
text-align: right;
|
|
706
|
+
}
|
|
707
|
+
|
|
573
708
|
/* ── Help ────────────────────────────────────────────────────────────────── */
|
|
574
709
|
|
|
575
710
|
/*
|