From 2ef10bd61f1f289ef76d95f0233cab55bceda319 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 19 Oct 2025 15:41:43 -0400 Subject: [PATCH 1/4] Add color handling for dark mode --- ts/a11y/complexity/collapse.ts | 2 +- ts/a11y/explorer.ts | 15 +++++ ts/a11y/explorer/Highlighter.ts | 41 +++--------- ts/a11y/explorer/Region.ts | 100 +++++++++++++++++++++++----- ts/output/chtml.ts | 9 +++ ts/output/chtml/Wrappers/maction.ts | 19 ++++++ ts/output/svg.ts | 8 +++ ts/output/svg/Wrappers/maction.ts | 18 +++++ ts/ui/dialog/DraggableDialog.ts | 47 ++++++++++++- ts/ui/menu/Menu.ts | 20 ++++-- ts/util/StyleJson.ts | 24 +++---- 11 files changed, 234 insertions(+), 69 deletions(-) diff --git a/ts/a11y/complexity/collapse.ts b/ts/a11y/complexity/collapse.ts index f20db9065..24ce13109 100644 --- a/ts/a11y/complexity/collapse.ts +++ b/ts/a11y/complexity/collapse.ts @@ -599,7 +599,7 @@ export class Collapse { ), }, [ - factory.create('mtext', { mathcolor: 'blue', ...variant }, [ + factory.create('mtext', variant, [ (factory.create('text') as TextNode).setText(marker), ]), ] diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index 8df549bc6..575533aed 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -418,6 +418,21 @@ export function ExplorerMathDocumentMixin< display: 'inline-flex', 'align-items': 'center', }, + '@media (prefers-color-scheme: dark) /* explorer */': { + 'mjx-help > svg': { + stroke: '#E0E0E0', + }, + 'mjx-help > svg > circle': { + fill: '#404040', + }, + 'mjx-help > svg > circle:nth-child(2)': { + fill: 'rgba(132, 132, 255, .3)', + }, + 'mjx-help:hover > svg > circle:nth-child(2)': { + stroke: '#AAAAAA', + fill: '#404040', + }, + } }; /** diff --git a/ts/a11y/explorer/Highlighter.ts b/ts/a11y/explorer/Highlighter.ts index 7f520914c..27d5c864f 100644 --- a/ts/a11y/explorer/Highlighter.ts +++ b/ts/a11y/explorer/Highlighter.ts @@ -21,26 +21,9 @@ interface NamedColor { color: string; alpha?: number; + type?: string; } -interface ChannelColor { - red: number; - green: number; - blue: number; - alpha?: number; -} - -const namedColors: { [key: string]: ChannelColor } = { - red: { red: 255, green: 0, blue: 0 }, - green: { red: 0, green: 255, blue: 0 }, - blue: { red: 0, green: 0, blue: 255 }, - yellow: { red: 255, green: 255, blue: 0 }, - cyan: { red: 0, green: 255, blue: 255 }, - magenta: { red: 255, green: 0, blue: 255 }, - white: { red: 255, green: 255, blue: 255 }, - black: { red: 0, green: 0, blue: 0 }, -}; - /** * Turns a named color into a channel color. * @@ -49,30 +32,22 @@ const namedColors: { [key: string]: ChannelColor } = { * @returns {string} The channel color. */ function getColorString(color: NamedColor, deflt: NamedColor): string { - const channel = namedColors[color.color] || namedColors[deflt.color]; - channel.alpha = color.alpha ?? deflt.alpha; - return rgba(channel); -} - -/** - * RGBa string version of the channel color. - * - * @param {ChannelColor} color The channel color. - * @returns {string} The color in RGBa format. - */ -function rgba(color: ChannelColor): string { - return `rgba(${color.red},${color.green},${color.blue},${color.alpha ?? 1})`; + const type = deflt.type; + const name = color.color ?? deflt.color; + const opacity = color.alpha ?? deflt.alpha; + const alpha = opacity === 1 ? 1 : `var(--mjx-${type}-alpha)`; + return `rgba(var(--mjx-${type}-${name}), ${alpha})`; } /** * The default background color if a none existing color is provided. */ -const DEFAULT_BACKGROUND: NamedColor = { color: 'blue', alpha: 1 }; +const DEFAULT_BACKGROUND: NamedColor = { color: 'blue', alpha: 1, type: 'bg' }; /** * The default color if a none existing color is provided. */ -const DEFAULT_FOREGROUND: NamedColor = { color: 'black', alpha: 1 }; +const DEFAULT_FOREGROUND: NamedColor = { color: 'black', alpha: 1, type: 'fg' }; export interface Highlighter { /** diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 109a42d2b..374288c89 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -73,13 +73,6 @@ export abstract class AbstractRegion implements Region { */ protected static className: string; - /** - * True if the style has already been added to the document. - * - * @type {boolean} - */ - protected static styleAdded: boolean = false; - /** * The CSS style that needs to be added for this type of region. * @@ -117,20 +110,24 @@ export abstract class AbstractRegion implements Region { this.AddStyles(); } + /** + * @returns {string} The stylesheet ID + */ + public static get sheetId(): string { + return 'MJX-' + this.name + '-styles'; + } + /** * @override */ public AddStyles() { - if (this.CLASS.styleAdded) { + const id = this.CLASS.sheetId; + if (!this.CLASS.style || this.document.adaptor.head().querySelector('#' + id)) { return; } - // TODO: should that be added to document.documentStyleSheet()? - const node = this.document.adaptor.node('style'); + const node = this.document.adaptor.node('style', {id}); node.innerHTML = this.CLASS.style.cssText; - this.document.adaptor - .head(this.document.adaptor.document) - .appendChild(node); - this.CLASS.styleAdded = true; + this.document.adaptor.head().appendChild(node); } /** @@ -177,7 +174,7 @@ export abstract class AbstractRegion implements Region { */ public Hide() { if (!this.div) return; - this.div.parentNode.removeChild(this.div); + this.div.remove(); this.div = null; this.inner = null; } @@ -335,6 +332,13 @@ export class ToolTip extends StringRegion { 'border-radius': 'inherit', padding: '0 2px', }, + '@media (prefers-color-scheme: dark)': { + ['.' + ToolTip.className]: { + 'background-color': '#222025', + 'box-shadow': '0px 5px 20px #000', + border: '1px solid #7C7C7C', + }, + }, }); } @@ -348,6 +352,43 @@ export class LiveRegion extends StringRegion { * @override */ protected static style: StyleJsonSheet = new StyleJsonSheet({ + ':root': { + '--mjx-fg-red': '255, 0, 0', + '--mjx-fg-green': '0, 255, 0', + '--mjx-fg-blue': '0, 0, 255', + '--mjx-fg-yellow': '255, 255, 0', + '--mjx-fg-cyan': '0, 255, 255', + '--mjx-fg-magenta': '255, 0, 255', + '--mjx-fg-white': '255, 255, 255', + '--mjx-fg-black': '0, 0, 0', + '--mjx-bg-red': '255, 0, 0', + '--mjx-bg-green': '0, 255, 0', + '--mjx-bg-blue': '0, 0, 255', + '--mjx-bg-yellow': '255, 255, 0', + '--mjx-bg-cyan': '0, 255, 255', + '--mjx-bg-magenta': '255, 0, 255', + '--mjx-bg-white': '255, 255, 255', + '--mjx-bg-black': '0, 0, 0', + '--mjx-live-bg-color': 'white', + '--mjx-live-shadow-color': '#888', + '--mjx-live-border-color': '#CCCCCC', + '--mjx-bg-alpha': 0.2, + '--mjx-fg-alpha': 1, + }, + '@media (prefers-color-scheme: dark)': { + ':root': { + '--mjx-bg-blue': '132, 132, 255', + '--mjx-bg-white': '0, 0, 0', + '--mjx-bg-black': '255, 255, 255', + '--mjx-fg-white': '0, 0, 0', + '--mjx-fg-black': '255, 255, 255', + '--mjx-live-bg-color': '#222025', + '--mjx-live-shadow-color': 'black', + '--mjx-live-border-color': '#7C7C7C', + '--mjx-bg-alpha': 0.3, + '--mjx-fg-alpha': 1, + }, + }, ['.' + LiveRegion.className]: { position: 'absolute', top: 0, @@ -360,20 +401,36 @@ export class LiveRegion extends StringRegion { left: 0, right: 0, margin: '0 auto', - 'background-color': 'white', - 'box-shadow': '0px 5px 20px #888', - border: '2px solid #CCCCCC', + 'background-color': 'var(--mjx-live-bg-color)', + 'box-shadow': '0px 5px 20px var(--mjx-live-shadow-color)', + border: '2px solid var(--mjx-live-border-color)', }, ['.' + LiveRegion.className + '_Show']: { display: 'block', }, }); + + /** + * @param {string} type The type of alpha to set (fg or bg) + * @param {number} alpha The alpha value to use for the background + * @param {Document} document The document whose CSS styles are to be adjusted + */ + public static setAlpha(type: string, alpha: number, document: Document) { + const style = document.head.querySelector('#' + this.sheetId) as HTMLStyleElement; + if (style) { + const name = `--mjx-${type}-alpha`; + (style.sheet.cssRules[0] as any).style.setProperty(name, alpha); + (style.sheet.cssRules[1] as any).cssRules[0].style.setProperty(name, alpha ** 0.7071); + } + } } /** * Region class that enables auto voicing of content via SSML markup. */ export class SpeechRegion extends LiveRegion { + protected static style: StyleJsonSheet = null; + /** * Flag to activate auto voicing. */ @@ -583,6 +640,13 @@ export class HoverRegion extends AbstractRegion { ['.' + HoverRegion.className + ' > div']: { overflow: 'hidden', }, + '@media (prefers-color-scheme: dark)': { + ['.' + HoverRegion.className]: { + 'background-color': '#222025', + 'box-shadow': '0px 5px 20px #000', + border: '1px solid #7C7C7C', + }, + }, }); /** diff --git a/ts/output/chtml.ts b/ts/output/chtml.ts index 68bf483c0..5c33718fa 100644 --- a/ts/output/chtml.ts +++ b/ts/output/chtml.ts @@ -151,6 +151,15 @@ export class CHTML extends CommonOutputJax< 'mjx-container [inline-breaks]': { display: 'inline' }, + 'mjx-container .mjx-selected': { + outline: '2px solid black', + }, + '@media (prefers-color-scheme: dark)': { + 'mjx-container .mjx-selected': { + outline: '2px solid #C8C8C8', + }, + }, + // // These don't have Wrapper subclasses, so add their styles here // diff --git a/ts/output/chtml/Wrappers/maction.ts b/ts/output/chtml/Wrappers/maction.ts index eddafb694..9f66e54a8 100644 --- a/ts/output/chtml/Wrappers/maction.ts +++ b/ts/output/chtml/Wrappers/maction.ts @@ -188,6 +188,25 @@ export const ChtmlMaction = (function (): ChtmlMactionClass { 'background-color': '#F8F8F8', color: 'black', }, + 'mjx-maction[data-collapsible][toggle="1"]': { + color: '#55F', + }, + + '@media (prefers-color-scheme: dark) /* chtml maction */': { + 'mjx-tool > mjx-tip': { + border: '1px solid #888', + 'background-color': '#303030', + color: '#E0E0E0', + 'box-shadow': '2px 2px 5px #000', + }, + 'mjx-status': { + 'background-color': '#303030', + color: '#E0E0E0', + }, + 'mjx-maction[data-collapsible][toggle="1"]': { + color: '#88F', + }, + }, }; /** diff --git a/ts/output/svg.ts b/ts/output/svg.ts index 38844e48d..179043986 100644 --- a/ts/output/svg.ts +++ b/ts/output/svg.ts @@ -114,6 +114,14 @@ export class SVG extends CommonOutputJax< stroke: 'black', 'stroke-width': '80px', }, + '@media (prefers-color-scheme: dark)': { + [[ + 'rect[data-sre-highlighter-added]:has(+ .mjx-selected)', + 'rect[data-sre-highlighter-bbox].mjx-selected', + ].join(', ')]: { + stroke: '#C8C8C8', + }, + }, }; /** diff --git a/ts/output/svg/Wrappers/maction.ts b/ts/output/svg/Wrappers/maction.ts index 0a551cce2..298c40f11 100644 --- a/ts/output/svg/Wrappers/maction.ts +++ b/ts/output/svg/Wrappers/maction.ts @@ -187,6 +187,24 @@ export const SvgMaction = (function (): SvgMactionClass { 'background-color': '#F8F8F8', color: 'black', }, + 'g[data-mml-node="maction"][data-collapsible][data-toggle="1"]': { + fill: '#55F', + }, + + '@media (prefers-color-scheme: dark) /* svg maction */': { + 'mjx-tool > mjx-tip': { + 'background-color': '#303030', + color: '#E0E0E0', + 'box-shadow': '2px 2px 5px #000', + }, + 'mjx-status': { + 'background-color': '#303030', + color: '#E0E0E0', + }, + 'g[data-mml-node="maction"][data-collapsible][data-toggle="1"]': { + fill: '#88F', + }, + }, }; /** diff --git a/ts/ui/dialog/DraggableDialog.ts b/ts/ui/dialog/DraggableDialog.ts index a7aa784c7..5c8a3cbd7 100644 --- a/ts/ui/dialog/DraggableDialog.ts +++ b/ts/ui/dialog/DraggableDialog.ts @@ -173,6 +173,7 @@ export class DraggableDialog { * The default styles for all dialogs */ public static styles: StyleJson = { + // // For when dialog element is not available // @@ -296,7 +297,7 @@ export class DraggableDialog { 'line-height': 0, padding: '8px 0 6px', }, - '.mjs-dialog-button > mjx-dialog-icon:hover': { + '.mjx-dialog-button > mjx-dialog-icon:hover': { 'background-color': '#CCC !important', }, @@ -404,6 +405,50 @@ export class DraggableDialog { bottom: '-3px', cursor: 'nwse-resize', }, + + '@media (prefers-color-scheme: dark)': { + '.mjx-dialog': { + 'background-color': '#303030', + 'box-shadow': '0px 10px 20px #000', + border: '3px outset #7C7C7C', + }, + 'mjx-dialog': { + color: '#E0E0E0', + }, + 'mjx-dialog > div': { + border: '2px inset #7C7C7C', + 'background-color': '#222025', + }, + 'a[href]': { + color: '#86A7F5', + }, + 'a[href]:visited': { + color: '#DD98E2', + }, + 'mjx-dialog kbd': { + color: '#F8F8F8', + 'background-color': '#545454', + border: 'solid 1.5px #7A7C7E', + 'border-bottom-color': '#707070', + 'box-shadow': 'inset -.5px -1px 0 #818589', + }, + '.mjx-dialog-button': { + border: '2px solid #686868', + color: '#A4A4A4', + }, + '.mjx-dialog-button:hover': { + color: '#CBCBCB !important', + border: '2px solid #888888 !important', + }, + '.mjx-dialog-button > mjx-dialog-icon': { + 'background-color': '#646464', + }, + '.mjx-dialog-button > mjx-dialog-icon:hover': { + 'background-color': '#888888 !important', + }, + }, + + }; protected static helpMessage: string = ` diff --git a/ts/ui/menu/Menu.ts b/ts/ui/menu/Menu.ts index 2f91ca01f..68eb5c8f2 100644 --- a/ts/ui/menu/Menu.ts +++ b/ts/ui/menu/Menu.ts @@ -621,9 +621,9 @@ export class Menu { ), this.a11yVar('highlight', (value) => this.setHighlight(value)), this.a11yVar('backgroundColor'), - this.a11yVar('backgroundOpacity'), + this.a11yVar('backgroundOpacity', (value) => this.setAlpha('bg', value)), this.a11yVar('foregroundColor'), - this.a11yVar('foregroundOpacity'), + this.a11yVar('foregroundOpacity', (value) => this.setAlpha('fg', value)), this.a11yVar('subtitles'), this.a11yVar('viewBraille'), this.a11yVar('voicing'), @@ -922,8 +922,7 @@ export class Menu { AnnotationMenu.copyAnnotations(cache), '', ]); - CssStyles.addInfoStyles(this.document.document as any); - CssStyles.addMenuStyles(this.document.document as any); + CssStyles.addMenuStyles(this.document.document as Document); } /** @@ -1075,6 +1074,8 @@ export class Menu { if (renderer !== this.defaultSettings.renderer) { this.document.whenReady(() => this.setRenderer(renderer, false)); } + this.setAlpha('fg', this.settings.foregroundOpacity ?? '100'); + this.setAlpha('bg', this.settings.backgroundOpacity ?? '20'); }); } @@ -1327,6 +1328,17 @@ export class Menu { } } + /** + * @param {string} type The type of alpha to set (fg or bg) + * @param {string} value The value to set it to + */ + protected setAlpha(type: string, value: string) { + if (MathJax._?.a11y?.explorer) { + const alpha = parseInt(value) / 100; + MathJax._.a11y.explorer.Region.LiveRegion.setAlpha(type, alpha, this.document.document); + } + } + /** * Request the scaling value from the user and save it in the settings */ diff --git a/ts/util/StyleJson.ts b/ts/util/StyleJson.ts index a27de84fa..5755ab3eb 100644 --- a/ts/util/StyleJson.ts +++ b/ts/util/StyleJson.ts @@ -32,7 +32,7 @@ export type StyleJsonData = { * A list of selectors and their data (basically a stylesheet) */ export type StyleJson = { - [selector: string]: StyleJsonData; + [selector: string]: StyleJsonData | StyleJson; }; /******************************************************************************/ @@ -97,32 +97,32 @@ export class StyleJsonSheet { } /** + * @param {StyleJson} styles The style list to convert * @returns {string[]} An array of rule strings for the style list */ - public getStyleRules(): string[] { - const selectors = Object.keys(this.styles); + public getStyleRules(styles: StyleJson = this.styles): string[] { + const selectors = Object.keys(styles); const defs: string[] = new Array(selectors.length); let i = 0; for (const selector of selectors) { - defs[i++] = - selector + - ' {\n' + - this.getStyleDefString(this.styles[selector]) + - '\n}'; + const data = styles[selector]; + defs[i++] = `${selector} {\n${this.getStyleDefString(data)}\n}`; } return defs; } /** - * @param {StyleJsonData} styles The style data to be stringified - * @returns {string} The CSS string for the given data + * @param {StyleJsonData | StyleJson} styles The style data to be stringified + * @returns {string} The CSS string for the given data */ - public getStyleDefString(styles: StyleJsonData): string { + public getStyleDefString(styles: StyleJsonData | StyleJson): string { const properties = Object.keys(styles); const values: string[] = new Array(properties.length); let i = 0; for (const property of properties) { - values[i++] = ' ' + property + ': ' + styles[property] + ';'; + values[i++] = styles[property] instanceof Object + ? ' ' + this.getStyleRules({[property]: styles[property]} as StyleJson).join('\n ') + : ' ' + property + ': ' + styles[property] + ';'; } return values.join('\n'); } From 2df99776d5f48480f852d806c6684658df71993f Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 19 Oct 2025 16:33:33 -0400 Subject: [PATCH 2/4] Fixes for prettier --- ts/a11y/explorer.ts | 2 +- ts/a11y/explorer/Region.ts | 16 ++++++++++++---- ts/ui/dialog/DraggableDialog.ts | 3 --- ts/ui/menu/Menu.ts | 14 +++++++++++--- ts/util/StyleJson.ts | 10 +++++++--- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts index 575533aed..a24aedb37 100644 --- a/ts/a11y/explorer.ts +++ b/ts/a11y/explorer.ts @@ -432,7 +432,7 @@ export function ExplorerMathDocumentMixin< stroke: '#AAAAAA', fill: '#404040', }, - } + }, }; /** diff --git a/ts/a11y/explorer/Region.ts b/ts/a11y/explorer/Region.ts index 374288c89..9feaabb23 100644 --- a/ts/a11y/explorer/Region.ts +++ b/ts/a11y/explorer/Region.ts @@ -122,10 +122,13 @@ export abstract class AbstractRegion implements Region { */ public AddStyles() { const id = this.CLASS.sheetId; - if (!this.CLASS.style || this.document.adaptor.head().querySelector('#' + id)) { + if ( + !this.CLASS.style || + this.document.adaptor.head().querySelector('#' + id) + ) { return; } - const node = this.document.adaptor.node('style', {id}); + const node = this.document.adaptor.node('style', { id }); node.innerHTML = this.CLASS.style.cssText; this.document.adaptor.head().appendChild(node); } @@ -416,11 +419,16 @@ export class LiveRegion extends StringRegion { * @param {Document} document The document whose CSS styles are to be adjusted */ public static setAlpha(type: string, alpha: number, document: Document) { - const style = document.head.querySelector('#' + this.sheetId) as HTMLStyleElement; + const style = document.head.querySelector( + '#' + this.sheetId + ) as HTMLStyleElement; if (style) { const name = `--mjx-${type}-alpha`; (style.sheet.cssRules[0] as any).style.setProperty(name, alpha); - (style.sheet.cssRules[1] as any).cssRules[0].style.setProperty(name, alpha ** 0.7071); + (style.sheet.cssRules[1] as any).cssRules[0].style.setProperty( + name, + alpha ** 0.7071 + ); } } } diff --git a/ts/ui/dialog/DraggableDialog.ts b/ts/ui/dialog/DraggableDialog.ts index 5c8a3cbd7..5e981a225 100644 --- a/ts/ui/dialog/DraggableDialog.ts +++ b/ts/ui/dialog/DraggableDialog.ts @@ -173,7 +173,6 @@ export class DraggableDialog { * The default styles for all dialogs */ public static styles: StyleJson = { - // // For when dialog element is not available // @@ -447,8 +446,6 @@ export class DraggableDialog { 'background-color': '#888888 !important', }, }, - - }; protected static helpMessage: string = ` diff --git a/ts/ui/menu/Menu.ts b/ts/ui/menu/Menu.ts index 68eb5c8f2..90abfdbdf 100644 --- a/ts/ui/menu/Menu.ts +++ b/ts/ui/menu/Menu.ts @@ -621,9 +621,13 @@ export class Menu { ), this.a11yVar('highlight', (value) => this.setHighlight(value)), this.a11yVar('backgroundColor'), - this.a11yVar('backgroundOpacity', (value) => this.setAlpha('bg', value)), + this.a11yVar('backgroundOpacity', (value) => + this.setAlpha('bg', value) + ), this.a11yVar('foregroundColor'), - this.a11yVar('foregroundOpacity', (value) => this.setAlpha('fg', value)), + this.a11yVar('foregroundOpacity', (value) => + this.setAlpha('fg', value) + ), this.a11yVar('subtitles'), this.a11yVar('viewBraille'), this.a11yVar('voicing'), @@ -1335,7 +1339,11 @@ export class Menu { protected setAlpha(type: string, value: string) { if (MathJax._?.a11y?.explorer) { const alpha = parseInt(value) / 100; - MathJax._.a11y.explorer.Region.LiveRegion.setAlpha(type, alpha, this.document.document); + MathJax._.a11y.explorer.Region.LiveRegion.setAlpha( + type, + alpha, + this.document.document + ); } } diff --git a/ts/util/StyleJson.ts b/ts/util/StyleJson.ts index 5755ab3eb..61611cd48 100644 --- a/ts/util/StyleJson.ts +++ b/ts/util/StyleJson.ts @@ -120,9 +120,13 @@ export class StyleJsonSheet { const values: string[] = new Array(properties.length); let i = 0; for (const property of properties) { - values[i++] = styles[property] instanceof Object - ? ' ' + this.getStyleRules({[property]: styles[property]} as StyleJson).join('\n ') - : ' ' + property + ': ' + styles[property] + ';'; + values[i++] = + styles[property] instanceof Object + ? ' ' + + this.getStyleRules({ + [property]: styles[property], + } as StyleJson).join('\n ') + : ' ' + property + ': ' + styles[property] + ';'; } return values.join('\n'); } From b459898745b2eba7608b820dd9b5fec0ad4c0db1 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 26 Oct 2025 15:53:40 -0400 Subject: [PATCH 3/4] Update spacing for StyleJason for @media rules, and add a test for it --- testsuite/tests/util/StyleJson.test.ts | 16 +++++++++++++++ ts/util/StyleJson.ts | 28 +++++++++++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/testsuite/tests/util/StyleJson.test.ts b/testsuite/tests/util/StyleJson.test.ts index 84a013515..08d76dd2c 100644 --- a/testsuite/tests/util/StyleJson.test.ts +++ b/testsuite/tests/util/StyleJson.test.ts @@ -36,4 +36,20 @@ describe('StyleJsonSheet object', () => { expect(styles.cssText).toBe(''); }); + test('Compound style', () => { + expect(new StyleJsonSheet({ + '@media (prefers-color-scheme: dark)': { + 'mjx-container': { + 'color': '#E0E0E0', + }, + } + }).cssText).toBe([ + '@media (prefers-color-scheme: dark) {', + ' mjx-container {', + ' color: #E0E0E0;', + ' }', + '}' + ].join('\n')); + }); + }); diff --git a/ts/util/StyleJson.ts b/ts/util/StyleJson.ts index 61611cd48..f08263576 100644 --- a/ts/util/StyleJson.ts +++ b/ts/util/StyleJson.ts @@ -100,13 +100,17 @@ export class StyleJsonSheet { * @param {StyleJson} styles The style list to convert * @returns {string[]} An array of rule strings for the style list */ - public getStyleRules(styles: StyleJson = this.styles): string[] { + public getStyleRules( + styles: StyleJson = this.styles, + spaces: string = '' + ): string[] { const selectors = Object.keys(styles); const defs: string[] = new Array(selectors.length); let i = 0; for (const selector of selectors) { const data = styles[selector]; - defs[i++] = `${selector} {\n${this.getStyleDefString(data)}\n}`; + defs[i++] = + `${spaces}${selector} {\n${this.getStyleDefString(data, spaces)}\n${spaces}}`; } return defs; } @@ -115,19 +119,25 @@ export class StyleJsonSheet { * @param {StyleJsonData | StyleJson} styles The style data to be stringified * @returns {string} The CSS string for the given data */ - public getStyleDefString(styles: StyleJsonData | StyleJson): string { + public getStyleDefString( + styles: StyleJsonData | StyleJson, + spaces: string + ): string { const properties = Object.keys(styles); const values: string[] = new Array(properties.length); let i = 0; for (const property of properties) { values[i++] = styles[property] instanceof Object - ? ' ' + - this.getStyleRules({ - [property]: styles[property], - } as StyleJson).join('\n ') - : ' ' + property + ': ' + styles[property] + ';'; + ? spaces + + this.getStyleRules( + { + [property]: styles[property], + } as StyleJson, + spaces + ' ' + ).join('\n' + spaces) + : ' ' + spaces + property + ': ' + styles[property] + ';'; } - return values.join('\n'); + return values.join('\n' + spaces); } } From e3d534f3a5a9cd2b2f0c23fc38bca94c8e6844ae Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Sun, 26 Oct 2025 15:57:44 -0400 Subject: [PATCH 4/4] Add missing JSDoc parameters --- ts/util/StyleJson.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/util/StyleJson.ts b/ts/util/StyleJson.ts index f08263576..c37e9e365 100644 --- a/ts/util/StyleJson.ts +++ b/ts/util/StyleJson.ts @@ -98,7 +98,8 @@ export class StyleJsonSheet { /** * @param {StyleJson} styles The style list to convert - * @returns {string[]} An array of rule strings for the style list + * @param {string} spaces The spaces to put at the beginning of each line + * @returns {string[]} An array of rule strings for the style list */ public getStyleRules( styles: StyleJson = this.styles, @@ -117,7 +118,8 @@ export class StyleJsonSheet { /** * @param {StyleJsonData | StyleJson} styles The style data to be stringified - * @returns {string} The CSS string for the given data + * @param {string} spaces The spaces to put at the beginning of each line + * @returns {string} The CSS string for the given data */ public getStyleDefString( styles: StyleJsonData | StyleJson,