diff --git a/css/mathfield.less b/css/mathfield.less index 92ae2d5d7..ff6a79dd4 100644 --- a/css/mathfield.less +++ b/css/mathfield.less @@ -121,8 +121,36 @@ ); } +:host([theme='dark']) .ML__container { + --_contains-highlight-color: var( + --contains-highlight-color, + hsl(var(--_hue), 85%, 75%) + ); + --_caret-color: var(--caret-color, hsl(var(--_hue), 65%, 55%)); + --_selection-color: var(--selection-color, #fff); + --_selection-background-color: var( + --selection-background-color, + hsl(var(--_hue), 65%, 55%) + ); + --_text-highlight-background-color: var( + --text-highlight-background-color, + hsla(var(--_hue), 40%, 50%, 0.6) + ); + --_contains-highlight-background-color: var( + --contains-highlight-background-color, + hsl(var(--_hue), 5%, 34%) + ); + --_latex-color: var(--primary, hsl(var(--_hue), 40%, 50%)); + --_composition-background-color: #69571c; + --_composition-text-color: white; + --_placeholder-color: hsl(var(--_hue), 60%, 69%); + + --_smart-fence-color: var(--smart-fence-color, #fff); + --_smart-fence-opacity: var(--smart-fence-opacity, 0.7); +} + @media (prefers-color-scheme: dark) { - .ML__container { + :host(:not([theme='light'])) .ML__container { --_contains-highlight-color: var( --contains-highlight-color, hsl(var(--_hue), 85%, 75%) diff --git a/css/virtual-keyboard.less b/css/virtual-keyboard.less index 785dd5765..8b39e219a 100644 --- a/css/virtual-keyboard.less +++ b/css/virtual-keyboard.less @@ -1070,7 +1070,6 @@ Note there are a different set of tooltip rules for the keyboard toggle --keyboard-toolbar-background-hover, #303030 ); - --keyboard-toolbar-background-hover: #303030; --_horizontal-rule: var(--keyboard-horizontal-rule, 1px solid #303030); @@ -1086,8 +1085,6 @@ Note there are a different set of tooltip rules for the keyboard toggle #4d5154 ); --_keycap-secondary-text: var(--keycap-secondary-text, #e7ebee); - --keycap-secondary-border: transparent; - --keycap-secondary-border-bottom: transparent; --_keycap-secondary-border: var(--keycap-secondary-border, transparent); --_keycap-secondary-border-bottom: var( --keycap-secondary-border-bottom, @@ -1110,7 +1107,6 @@ Note there are a different set of tooltip rules for the keyboard toggle --keyboard-toolbar-background-hover, #303030 ); - --keyboard-toolbar-background-hover: #303030; --_horizontal-rule: var(--keyboard-horizontal-rule, 1px solid #303030); @@ -1126,8 +1122,6 @@ Note there are a different set of tooltip rules for the keyboard toggle #4d5154 ); --_keycap-secondary-text: var(--keycap-secondary-text, #e7ebee); - --keycap-secondary-border: transparent; - --keycap-secondary-border-bottom: transparent; --_keycap-secondary-border: var(--keycap-secondary-border, transparent); --_keycap-secondary-border-bottom: var( --keycap-secondary-border-bottom, diff --git a/src/ui/colors/colors.less b/src/ui/colors/colors.less index aacc6a2e2..23260634b 100644 --- a/src/ui/colors/colors.less +++ b/src/ui/colors/colors.less @@ -167,7 +167,7 @@ } @media (prefers-color-scheme: dark) { - :host { + :host(:not([theme='light'])) { --semantic-blue: var(--blue-700); --semantic-red: var(--red-400); --semantic-orange: var(--orange-400); diff --git a/src/ui/style.less b/src/ui/style.less index 4e3981f9b..7f6632c5c 100644 --- a/src/ui/style.less +++ b/src/ui/style.less @@ -75,7 +75,7 @@ } @media (prefers-color-scheme: dark) { - :host { + :host(:not([theme='light'])) { --ui-menu-bg: var(--neutral-200); } } diff --git a/test/playwright-tests/theme.spec.ts b/test/playwright-tests/theme.spec.ts new file mode 100644 index 000000000..f8fec70e7 --- /dev/null +++ b/test/playwright-tests/theme.spec.ts @@ -0,0 +1,112 @@ +import { test, expect } from '@playwright/test'; + +const LIGHT_NEUTRAL_100 = 'rgb(245, 245, 245)'; +const DARK_NEUTRAL_100 = 'rgb(18, 18, 18)'; + +const readVar = (id: string, varName: string) => ` + (() => { + const mf = document.getElementById(${JSON.stringify(id)}); + mf.style.setProperty('outline-color', 'var(${varName})'); + return getComputedStyle(mf).outlineColor; + })() +`; + +const readCaretColor = (id: string) => ` + (() => { + const mf = document.getElementById(${JSON.stringify(id)}); + const container = mf.shadowRoot.querySelector('.ML__container'); + container.style.setProperty('outline-color', 'var(--_caret-color)'); + return getComputedStyle(container).outlineColor; + })() +`; + +test.beforeEach(async ({ page }) => { + await page.goto('/dist/playwright-test-page/'); + await page.waitForSelector('math-field', { timeout: 5000 }); +}); + +test('theme="light" overrides prefers-color-scheme: dark', async ({ page }) => { + await page.emulateMedia({ colorScheme: 'dark' }); + + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + DARK_NEUTRAL_100 + ); + + await page.evaluate(() => { + document.getElementById('mf-1')!.setAttribute('theme', 'light'); + }); + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + LIGHT_NEUTRAL_100 + ); +}); + +test('theme="dark" overrides prefers-color-scheme: light', async ({ page }) => { + await page.emulateMedia({ colorScheme: 'light' }); + + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + LIGHT_NEUTRAL_100 + ); + + await page.evaluate(() => { + document.getElementById('mf-1')!.setAttribute('theme', 'dark'); + }); + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + DARK_NEUTRAL_100 + ); +}); + +test('removing theme attribute restores system preference', async ({ page }) => { + await page.emulateMedia({ colorScheme: 'dark' }); + + await page.evaluate(() => { + document.getElementById('mf-1')!.setAttribute('theme', 'light'); + }); + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + LIGHT_NEUTRAL_100 + ); + + await page.evaluate(() => { + document.getElementById('mf-1')!.removeAttribute('theme'); + }); + expect(await page.evaluate(readVar('mf-1', '--neutral-100'))).toBe( + DARK_NEUTRAL_100 + ); +}); + +test('mathfield.less container variables respect theme attribute', async ({ + page, +}) => { + await page.emulateMedia({ colorScheme: 'dark' }); + const darkCaret = await page.evaluate(readCaretColor('mf-1')); + + await page.evaluate(() => { + document.getElementById('mf-1')!.setAttribute('theme', 'light'); + }); + const lightCaret = await page.evaluate(readCaretColor('mf-1')); + + expect(darkCaret).not.toBe(lightCaret); + expect(darkCaret).not.toBe(''); + expect(lightCaret).not.toBe(''); +}); + +test('theme="dark" on light OS applies dark container palette', async ({ + page, +}) => { + await page.emulateMedia({ colorScheme: 'light' }); + const lightCaret = await page.evaluate(readCaretColor('mf-1')); + + await page.evaluate(() => { + document.getElementById('mf-1')!.setAttribute('theme', 'dark'); + }); + const darkCaret = await page.evaluate(readCaretColor('mf-1')); + + expect(darkCaret).not.toBe(lightCaret); + + // Sanity: this dark caret should match the one produced by dark OS with no theme attribute. + await page.evaluate(() => { + document.getElementById('mf-1')!.removeAttribute('theme'); + }); + await page.emulateMedia({ colorScheme: 'dark' }); + const osDarkCaret = await page.evaluate(readCaretColor('mf-1')); + expect(darkCaret).toBe(osDarkCaret); +});