Skip to content

Commit ee3e9b4

Browse files
authored
Merge pull request #1369 from mathjax/feature/dark-mode
Add color handling for dark mode
2 parents 1135887 + b6c54a1 commit ee3e9b4

File tree

12 files changed

+280
-70
lines changed

12 files changed

+280
-70
lines changed

testsuite/tests/util/StyleJson.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,20 @@ describe('StyleJsonSheet object', () => {
3636
expect(styles.cssText).toBe('');
3737
});
3838

39+
test('Compound style', () => {
40+
expect(new StyleJsonSheet({
41+
'@media (prefers-color-scheme: dark)': {
42+
'mjx-container': {
43+
'color': '#E0E0E0',
44+
},
45+
}
46+
}).cssText).toBe([
47+
'@media (prefers-color-scheme: dark) {',
48+
' mjx-container {',
49+
' color: #E0E0E0;',
50+
' }',
51+
'}'
52+
].join('\n'));
53+
});
54+
3955
});

ts/a11y/complexity/collapse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ export class Collapse {
599599
),
600600
},
601601
[
602-
factory.create('mtext', { mathcolor: 'blue', ...variant }, [
602+
factory.create('mtext', variant, [
603603
(factory.create('text') as TextNode).setText(marker),
604604
]),
605605
]

ts/a11y/explorer.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,21 @@ export function ExplorerMathDocumentMixin<
418418
display: 'inline-flex',
419419
'align-items': 'center',
420420
},
421+
'@media (prefers-color-scheme: dark) /* explorer */': {
422+
'mjx-help > svg': {
423+
stroke: '#E0E0E0',
424+
},
425+
'mjx-help > svg > circle': {
426+
fill: '#404040',
427+
},
428+
'mjx-help > svg > circle:nth-child(2)': {
429+
fill: 'rgba(132, 132, 255, .3)',
430+
},
431+
'mjx-help:hover > svg > circle:nth-child(2)': {
432+
stroke: '#AAAAAA',
433+
fill: '#404040',
434+
},
435+
},
421436
};
422437

423438
/**

ts/a11y/explorer/Highlighter.ts

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,9 @@
2121
interface NamedColor {
2222
color: string;
2323
alpha?: number;
24+
type?: string;
2425
}
2526

26-
interface ChannelColor {
27-
red: number;
28-
green: number;
29-
blue: number;
30-
alpha?: number;
31-
}
32-
33-
const namedColors: { [key: string]: ChannelColor } = {
34-
red: { red: 255, green: 0, blue: 0 },
35-
green: { red: 0, green: 255, blue: 0 },
36-
blue: { red: 0, green: 0, blue: 255 },
37-
yellow: { red: 255, green: 255, blue: 0 },
38-
cyan: { red: 0, green: 255, blue: 255 },
39-
magenta: { red: 255, green: 0, blue: 255 },
40-
white: { red: 255, green: 255, blue: 255 },
41-
black: { red: 0, green: 0, blue: 0 },
42-
};
43-
4427
/**
4528
* Turns a named color into a channel color.
4629
*
@@ -49,30 +32,22 @@ const namedColors: { [key: string]: ChannelColor } = {
4932
* @returns {string} The channel color.
5033
*/
5134
function getColorString(color: NamedColor, deflt: NamedColor): string {
52-
const channel = namedColors[color.color] || namedColors[deflt.color];
53-
channel.alpha = color.alpha ?? deflt.alpha;
54-
return rgba(channel);
55-
}
56-
57-
/**
58-
* RGBa string version of the channel color.
59-
*
60-
* @param {ChannelColor} color The channel color.
61-
* @returns {string} The color in RGBa format.
62-
*/
63-
function rgba(color: ChannelColor): string {
64-
return `rgba(${color.red},${color.green},${color.blue},${color.alpha ?? 1})`;
35+
const type = deflt.type;
36+
const name = color.color ?? deflt.color;
37+
const opacity = color.alpha ?? deflt.alpha;
38+
const alpha = opacity === 1 ? 1 : `var(--mjx-${type}-alpha)`;
39+
return `rgba(var(--mjx-${type}-${name}), ${alpha})`;
6540
}
6641

6742
/**
6843
* The default background color if a none existing color is provided.
6944
*/
70-
const DEFAULT_BACKGROUND: NamedColor = { color: 'blue', alpha: 1 };
45+
const DEFAULT_BACKGROUND: NamedColor = { color: 'blue', alpha: 1, type: 'bg' };
7146

7247
/**
7348
* The default color if a none existing color is provided.
7449
*/
75-
const DEFAULT_FOREGROUND: NamedColor = { color: 'black', alpha: 1 };
50+
const DEFAULT_FOREGROUND: NamedColor = { color: 'black', alpha: 1, type: 'fg' };
7651

7752
export interface Highlighter {
7853
/**

ts/a11y/explorer/Region.ts

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,6 @@ export abstract class AbstractRegion<T> implements Region<T> {
7373
*/
7474
protected static className: string;
7575

76-
/**
77-
* True if the style has already been added to the document.
78-
*
79-
* @type {boolean}
80-
*/
81-
protected static styleAdded: boolean = false;
82-
8376
/**
8477
* The CSS style that needs to be added for this type of region.
8578
*
@@ -117,20 +110,27 @@ export abstract class AbstractRegion<T> implements Region<T> {
117110
this.AddStyles();
118111
}
119112

113+
/**
114+
* @returns {string} The stylesheet ID
115+
*/
116+
public static get sheetId(): string {
117+
return 'MJX-' + this.name + '-styles';
118+
}
119+
120120
/**
121121
* @override
122122
*/
123123
public AddStyles() {
124-
if (this.CLASS.styleAdded) {
124+
const id = this.CLASS.sheetId;
125+
if (
126+
!this.CLASS.style ||
127+
this.document.adaptor.head().querySelector('#' + id)
128+
) {
125129
return;
126130
}
127-
// TODO: should that be added to document.documentStyleSheet()?
128-
const node = this.document.adaptor.node('style');
131+
const node = this.document.adaptor.node('style', { id });
129132
node.innerHTML = this.CLASS.style.cssText;
130-
this.document.adaptor
131-
.head(this.document.adaptor.document)
132-
.appendChild(node);
133-
this.CLASS.styleAdded = true;
133+
this.document.adaptor.head().appendChild(node);
134134
}
135135

136136
/**
@@ -177,7 +177,7 @@ export abstract class AbstractRegion<T> implements Region<T> {
177177
*/
178178
public Hide() {
179179
if (!this.div) return;
180-
this.div.parentNode.removeChild(this.div);
180+
this.div.remove();
181181
this.div = null;
182182
this.inner = null;
183183
}
@@ -335,6 +335,13 @@ export class ToolTip extends StringRegion {
335335
'border-radius': 'inherit',
336336
padding: '0 2px',
337337
},
338+
'@media (prefers-color-scheme: dark)': {
339+
['.' + ToolTip.className]: {
340+
'background-color': '#222025',
341+
'box-shadow': '0px 5px 20px #000',
342+
border: '1px solid #7C7C7C',
343+
},
344+
},
338345
});
339346
}
340347

@@ -348,6 +355,43 @@ export class LiveRegion extends StringRegion {
348355
* @override
349356
*/
350357
protected static style: StyleJsonSheet = new StyleJsonSheet({
358+
':root': {
359+
'--mjx-fg-red': '255, 0, 0',
360+
'--mjx-fg-green': '0, 255, 0',
361+
'--mjx-fg-blue': '0, 0, 255',
362+
'--mjx-fg-yellow': '255, 255, 0',
363+
'--mjx-fg-cyan': '0, 255, 255',
364+
'--mjx-fg-magenta': '255, 0, 255',
365+
'--mjx-fg-white': '255, 255, 255',
366+
'--mjx-fg-black': '0, 0, 0',
367+
'--mjx-bg-red': '255, 0, 0',
368+
'--mjx-bg-green': '0, 255, 0',
369+
'--mjx-bg-blue': '0, 0, 255',
370+
'--mjx-bg-yellow': '255, 255, 0',
371+
'--mjx-bg-cyan': '0, 255, 255',
372+
'--mjx-bg-magenta': '255, 0, 255',
373+
'--mjx-bg-white': '255, 255, 255',
374+
'--mjx-bg-black': '0, 0, 0',
375+
'--mjx-live-bg-color': 'white',
376+
'--mjx-live-shadow-color': '#888',
377+
'--mjx-live-border-color': '#CCCCCC',
378+
'--mjx-bg-alpha': 0.2,
379+
'--mjx-fg-alpha': 1,
380+
},
381+
'@media (prefers-color-scheme: dark)': {
382+
':root': {
383+
'--mjx-bg-blue': '132, 132, 255',
384+
'--mjx-bg-white': '0, 0, 0',
385+
'--mjx-bg-black': '255, 255, 255',
386+
'--mjx-fg-white': '0, 0, 0',
387+
'--mjx-fg-black': '255, 255, 255',
388+
'--mjx-live-bg-color': '#222025',
389+
'--mjx-live-shadow-color': 'black',
390+
'--mjx-live-border-color': '#7C7C7C',
391+
'--mjx-bg-alpha': 0.3,
392+
'--mjx-fg-alpha': 1,
393+
},
394+
},
351395
['.' + LiveRegion.className]: {
352396
position: 'absolute',
353397
top: 0,
@@ -360,20 +404,41 @@ export class LiveRegion extends StringRegion {
360404
left: 0,
361405
right: 0,
362406
margin: '0 auto',
363-
'background-color': 'white',
364-
'box-shadow': '0px 5px 20px #888',
365-
border: '2px solid #CCCCCC',
407+
'background-color': 'var(--mjx-live-bg-color)',
408+
'box-shadow': '0px 5px 20px var(--mjx-live-shadow-color)',
409+
border: '2px solid var(--mjx-live-border-color)',
366410
},
367411
['.' + LiveRegion.className + '_Show']: {
368412
display: 'block',
369413
},
370414
});
415+
416+
/**
417+
* @param {string} type The type of alpha to set (fg or bg)
418+
* @param {number} alpha The alpha value to use
419+
* @param {Document} document The document whose CSS styles are to be adjusted
420+
*/
421+
public static setAlpha(type: string, alpha: number, document: Document) {
422+
const style = document.head.querySelector(
423+
'#' + this.sheetId
424+
) as HTMLStyleElement;
425+
if (style) {
426+
const name = `--mjx-${type}-alpha`;
427+
(style.sheet.cssRules[0] as any).style.setProperty(name, alpha);
428+
(style.sheet.cssRules[1] as any).cssRules[0].style.setProperty(
429+
name,
430+
alpha ** 0.7071
431+
);
432+
}
433+
}
371434
}
372435

373436
/**
374437
* Region class that enables auto voicing of content via SSML markup.
375438
*/
376439
export class SpeechRegion extends LiveRegion {
440+
protected static style: StyleJsonSheet = null;
441+
377442
/**
378443
* Flag to activate auto voicing.
379444
*/
@@ -583,6 +648,13 @@ export class HoverRegion extends AbstractRegion<HTMLElement> {
583648
['.' + HoverRegion.className + ' > div']: {
584649
overflow: 'hidden',
585650
},
651+
'@media (prefers-color-scheme: dark)': {
652+
['.' + HoverRegion.className]: {
653+
'background-color': '#222025',
654+
'box-shadow': '0px 5px 20px #000',
655+
border: '1px solid #7C7C7C',
656+
},
657+
},
586658
});
587659

588660
/**

ts/output/chtml.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ export class CHTML<N, T, D> extends CommonOutputJax<
151151

152152
'mjx-container [inline-breaks]': { display: 'inline' },
153153

154+
'mjx-container .mjx-selected': {
155+
outline: '2px solid black',
156+
},
157+
'@media (prefers-color-scheme: dark)': {
158+
'mjx-container .mjx-selected': {
159+
outline: '2px solid #C8C8C8',
160+
},
161+
},
162+
154163
//
155164
// These don't have Wrapper subclasses, so add their styles here
156165
//

ts/output/chtml/Wrappers/maction.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ export const ChtmlMaction = (function <N, T, D>(): ChtmlMactionClass<N, T, D> {
188188
'background-color': '#F8F8F8',
189189
color: 'black',
190190
},
191+
'mjx-maction[data-collapsible][toggle="1"]': {
192+
color: '#55F',
193+
},
194+
195+
'@media (prefers-color-scheme: dark) /* chtml maction */': {
196+
'mjx-tool > mjx-tip': {
197+
border: '1px solid #888',
198+
'background-color': '#303030',
199+
color: '#E0E0E0',
200+
'box-shadow': '2px 2px 5px #000',
201+
},
202+
'mjx-status': {
203+
'background-color': '#303030',
204+
color: '#E0E0E0',
205+
},
206+
'mjx-maction[data-collapsible][toggle="1"]': {
207+
color: '#88F',
208+
},
209+
},
191210
};
192211

193212
/**

ts/output/svg.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ export class SVG<N, T, D> extends CommonOutputJax<
114114
stroke: 'black',
115115
'stroke-width': '80px',
116116
},
117+
'@media (prefers-color-scheme: dark)': {
118+
[[
119+
'rect[data-sre-highlighter-added]:has(+ .mjx-selected)',
120+
'rect[data-sre-highlighter-bbox].mjx-selected',
121+
].join(', ')]: {
122+
stroke: '#C8C8C8',
123+
},
124+
},
117125
};
118126

119127
/**

ts/output/svg/Wrappers/maction.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,24 @@ export const SvgMaction = (function <N, T, D>(): SvgMactionClass<N, T, D> {
187187
'background-color': '#F8F8F8',
188188
color: 'black',
189189
},
190+
'g[data-mml-node="maction"][data-collapsible][data-toggle="1"]': {
191+
fill: '#55F',
192+
},
193+
194+
'@media (prefers-color-scheme: dark) /* svg maction */': {
195+
'mjx-tool > mjx-tip': {
196+
'background-color': '#303030',
197+
color: '#E0E0E0',
198+
'box-shadow': '2px 2px 5px #000',
199+
},
200+
'mjx-status': {
201+
'background-color': '#303030',
202+
color: '#E0E0E0',
203+
},
204+
'g[data-mml-node="maction"][data-collapsible][data-toggle="1"]': {
205+
fill: '#88F',
206+
},
207+
},
190208
};
191209

192210
/**

0 commit comments

Comments
 (0)