diff --git a/__tests__/component/Viewer.spec.ts b/__tests__/component/Viewer.spec.ts
index b4c364f..02c5ee9 100644
--- a/__tests__/component/Viewer.spec.ts
+++ b/__tests__/component/Viewer.spec.ts
@@ -119,17 +119,31 @@ describe('Viewer.open()', () => {
expect(modalProps().show).toBe(true)
})
- it.each([
- ['light', true],
- ['dark', false],
- ['default', false],
- ] as const)('gives the modal a light backdrop only for a %s themed handler', async (theme, lightBackdrop) => {
- const handler = makeHandler({ id: theme, tagName: `oca-viewer-${theme}`, theme, enabled: () => true })
- const { vm, wrapper, modalProps } = mountViewer([handler])
- const f1 = makeFile()
- await vm.open([f1], f1)
- await wrapper.vm.$nextTick()
- expect(modalProps().lightBackdrop).toBe(lightBackdrop)
+ describe('under the user\'s theme', () => {
+ afterEach(() => document.body.style.removeProperty('--background-invert-if-dark'))
+
+ // The theme the user runs, the one a handler asks for, then whether the
+ // backdrop is light and whether it takes the theme's colours
+ it.each([
+ ['light', 'light', true, false],
+ ['light', 'dark', false, false],
+ ['light', 'default', true, true],
+ ['light', undefined, false, false],
+ ['dark', 'light', true, false],
+ ['dark', 'dark', false, false],
+ ['dark', 'default', false, true],
+ ['dark', undefined, false, false],
+ ] as const)('a %s theme gives a %s themed handler a light backdrop: %s, the theme\'s colours: %s', async (userTheme, theme, lightBackdrop, followsTheme) => {
+ document.body.style.setProperty('--background-invert-if-dark', userTheme === 'dark' ? 'invert(100%)' : 'no')
+ const id = theme ?? 'unset'
+ const handler = makeHandler({ id, tagName: `oca-viewer-${id}`, theme, enabled: () => true })
+ const { vm, wrapper, modalProps } = mountViewer([handler])
+ const f1 = makeFile()
+ await vm.open([f1], f1)
+ await wrapper.vm.$nextTick()
+ expect(modalProps().lightBackdrop).toBe(lightBackdrop)
+ expect(wrapper.find('.nc-modal-stub').classes('viewer__modal--default')).toBe(followsTheme)
+ })
})
})
diff --git a/__tests__/component/mountViewer.ts b/__tests__/component/mountViewer.ts
index 69f7efd..840f17f 100644
--- a/__tests__/component/mountViewer.ts
+++ b/__tests__/component/mountViewer.ts
@@ -17,9 +17,9 @@ import { registerTestHandlers } from '../factories.ts'
* Minimal NcModal stub.
*
* It renders the default slot and the `#actions` slot so we can assert on the
- * handler custom-element markup and drive the header actions. The style is
- * kept, as the real modal applies it: that is how the viewer makes room for
- * the sidebar. Navigation is
+ * handler custom-element markup and drive the header actions. The style and
+ * classes are kept, as the real modal applies them: that is how the viewer
+ * makes room for the sidebar and themes itself. Navigation is
* driven from tests through `findComponent(NcModalStub).vm.$emit('next'|'previous'|'close')`.
* The relevant props (`name`, `show`, `hasNext`, `hasPrevious`, `isComparing`
* related flags) are declared so they can be read back via `.props()`.
@@ -45,6 +45,7 @@ export const NcModalStub = defineComponent({
{
// What WCAG asks of normal text
expect(contrast).toBeGreaterThanOrEqual(4.5)
})
+
+ // The dark palette was set on the whole modal, so it reached into what a
+ // handler shows: a document in Text came out white on black under a
+ // light theme, and printed grey
+ test('leaves the colours of what a handler shows to the theme', async ({ page }) => {
+ const viewer = new ViewerPage(page)
+ await viewer.open('photo.jpg')
+ await viewer.waitForOpen()
+
+ const text = (selector: string) => page.locator(selector).first().evaluate((element) => getComputedStyle(element).getPropertyValue('--color-main-text').trim())
+ expect(await text('.viewer__modal .modal-header')).toBe('#ffffff')
+ expect(await text('.viewer__modal oca-viewer-image')).not.toBe('#ffffff')
+ })
})
diff --git a/lib/handlers.ts b/lib/handlers.ts
index f6c4265..c4aa044 100644
--- a/lib/handlers.ts
+++ b/lib/handlers.ts
@@ -94,7 +94,8 @@ export interface IHandler {
onInit?: () => Promise
/**
- * Viewer modal theme (one of 'dark', 'light', 'default')
+ * Viewer modal theme: 'dark' (the default), 'light', or 'default' to
+ * follow the user's theme
*/
theme?: 'dark' | 'light' | 'default'
diff --git a/lib/views/Viewer.vue b/lib/views/Viewer.vue
index d376ddc..cd0cb11 100644
--- a/lib/views/Viewer.vue
+++ b/lib/views/Viewer.vue
@@ -25,6 +25,7 @@
:slideshowPaused="slideshowPaused"
:spreadNavigation="true"
:style="{ width: isSidebarShown ? `${sidebarPosition}px` : null }"
+ :class="{ 'viewer__modal--default': followsTheme }"
class="viewer__modal"
size="full"
@close="close"
@@ -147,6 +148,7 @@
@@ -353,6 +355,8 @@ const canSwipe = ref(true)
const isFullscreen = ref(false)
const editing = ref(false)
const lightBackdrop = ref(false)
+// The handler asked for the user's own theme, backdrop and header included
+const followsTheme = ref(false)
const slideshowRunning = ref(false)
// Whether the current handler is playing media, which the slideshow waits for
const playing = ref(false)
@@ -1157,13 +1161,22 @@ const compare: ViewerAPI['compare'] = async (file1, file2, handlerId) => {
/**
* Handle Viewer opening to determine backdrop style.
*
- * The viewer is dark whatever theme the user runs: a photo or a video reads
- * better against dark, and the room around it should not compete with it.
- * A handler showing something else — a document, say — can ask for a light
- * backdrop instead, but nothing follows the user's theme here.
+ * The viewer is dark unless the handler asks otherwise: a photo or a video
+ * reads better against dark, and the room around it should not compete with
+ * it. A handler showing something else, a document say, can ask for a light
+ * backdrop, or for the user's own theme with 'default'.
*/
function onOpen() {
- lightBackdrop.value = (currentHandler.value?.theme ?? 'default') === 'light'
+ const theme = currentHandler.value?.theme ?? 'dark'
+ followsTheme.value = theme === 'default'
+ lightBackdrop.value = theme === 'light' || (followsTheme.value && isThemeLight())
+}
+
+/**
+ * Whether the user runs a light theme, the way the server's own styles tell.
+ */
+function isThemeLight(): boolean {
+ return getComputedStyle(document.body).getPropertyValue('--background-invert-if-dark').trim() !== 'invert(100%)'
}
/**
@@ -1734,17 +1747,32 @@ defineExpose({