Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/test/ipywidgets/ipywidgetsTheme.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { expect } from 'chai';
import * as fs from 'fs-extra';
import * as path from '../../platform/vscode-path/path';
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../constants.node';

suite('IPyWidgets Theming Bridge', () => {
test('Bridge CSS exists and maps key JupyterLab vars', async () => {
const cssPath = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src/webviews/webview-side/ipywidgets/renderer/jupyterlabThemeBridge.css'
);
expect(fs.pathExistsSync(cssPath)).to.equal(true, 'Bridge CSS file missing');
const css = fs.readFileSync(cssPath, 'utf8');
// Must scope to container to avoid global leaks
expect(css).to.include('.cell-output-ipywidget-background');
// Font family bridge
expect(css).to.match(/--jp-ui-font-family:\s*var\(--vscode-editor-font-family\)/);
// Foreground mapping
expect(css).to.match(/--jp-content-font-color0:\s*var\(--vscode-editor-foreground\)/);
// Border mapping
expect(css).to.match(/--jp-border-color1:\s*var\(--vscode-panel-border.*\)/);
// Widgets label color mapping
expect(css).to.match(/--jp-widgets-label-color:\s*var\(--vscode-editor-foreground\)/);
// Input color mappings
expect(css).to.match(/--jp-widgets-input-color:\s*var\(--vscode-input-foreground.*\)/);
expect(css).to.match(/--jp-widgets-input-background-color:\s*var\(/);
expect(css).to.match(/--jp-widgets-input-border-color:\s*var\(/);
});

test('Renderer imports the bridge CSS', async () => {
const rendererIndex = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src/webviews/webview-side/ipywidgets/renderer/index.ts'
);
const ts = fs.readFileSync(rendererIndex, 'utf8');
expect(ts).to.match(/import '\.\/jupyterlabThemeBridge\.css';/);
});

test('Kernel imports the bridge CSS to cover kernel-created containers', async () => {
const kernelIndex = path.join(
EXTENSION_ROOT_DIR_FOR_TESTS,
'src/webviews/webview-side/ipywidgets/kernel/index.ts'
);
const ts = fs.readFileSync(kernelIndex, 'utf8');
expect(ts).to.match(/import '\.\.\/renderer\/jupyterlabThemeBridge\.css';/);
});

test('No hardcoded white background for widget container', async () => {
const files = [
path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src/webviews/webview-side/ipywidgets/renderer/styles.css'),
path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src/webviews/webview-side/interactive-common/common.css')
];
for (const f of files) {
const css = fs.readFileSync(f, 'utf8');
const widgetBlockIndex = css.indexOf('.cell-output-ipywidget-background');
expect(widgetBlockIndex).to.be.greaterThan(-1, `${path.basename(f)} missing widget container style`);
// Ensure we don't force white background; allow transparent or theme var
expect(css).to.not.match(/\.cell-output-ipywidget-background[\s\S]*background:\s*white/i);
}
});
});
2 changes: 1 addition & 1 deletion src/webviews/webview-side/interactive-common/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ html {
}

.cell-output-ipywidget-background {
background: white !important;
background: transparent !important;
}

.cell-output-plot-background * {
Expand Down
3 changes: 3 additions & 0 deletions src/webviews/webview-side/ipywidgets/kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { ScriptManager } from './scriptManager';
import { IJupyterLabWidgetManagerCtor, INotebookModel } from './types';
import { NotebookMetadata } from '../../../../platform/common/utils';

// Ensure theme variables are available to any kernel-created containers
import '../renderer/jupyterlabThemeBridge.css';

class WidgetManagerComponent {
private readonly widgetManager: WidgetManager;
private readonly scriptManager: ScriptManager;
Expand Down
1 change: 1 addition & 0 deletions src/webviews/webview-side/ipywidgets/renderer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

import './styles.css';
import './jupyterlabThemeBridge.css';
import type * as nbformat from '@jupyterlab/nbformat';
import { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
import { createDeferred, Deferred } from '../../../../platform/common/utils/async';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Bridge VS Code webview theme variables to JupyterLab CSS variables
ipywidgets reads JupyterLab variables like --jp-layout-color*, --jp-content-font-color*.
We scope these to the ipywidget output container to avoid leaking styles.
*/

/* Base scope */
.cell-output-ipywidget-background {
/* Ensure readable default text */
color: var(--vscode-editor-foreground);
/* Fonts */
--jp-ui-font-family: var(--vscode-editor-font-family);
--jp-content-font-family: var(--vscode-editor-font-family);
--jp-code-font-family: var(--vscode-editor-font-family);
--jp-ui-font-size1: var(--vscode-font-size, 13px);
--jp-content-font-size1: var(--vscode-font-size, 14px);
--jp-code-font-size: var(--vscode-editor-font-size, 13px);

/* Foreground colors */
--jp-content-font-color0: var(--vscode-editor-foreground);
--jp-content-font-color1: var(--vscode-editor-foreground);
--jp-content-font-color2: var(--vscode-editor-foreground);
--jp-content-font-color3: var(--vscode-editor-foreground);
--jp-ui-font-color0: var(--vscode-editor-foreground);
--jp-ui-font-color1: var(--vscode-editor-foreground);
--jp-ui-font-color2: var(--vscode-editor-foreground);
--jp-ui-font-color3: var(--vscode-editor-foreground);

/* Inverse font colors are used on brand/success/etc backgrounds */
--jp-inverse-ui-font-color0: var(--vscode-button-foreground);
--jp-inverse-ui-font-color1: var(--vscode-button-foreground);
--jp-ui-inverse-font-color0: var(--vscode-button-foreground);
--jp-ui-inverse-font-color1: var(--vscode-button-foreground);

/* Brand colors and focus accents */
--jp-brand-color0: var(--vscode-button-background);
--jp-brand-color1: var(--vscode-textLink-foreground, var(--vscode-focusBorder));
--jp-brand-color2: var(--vscode-focusBorder);

/* Border width baseline */
--jp-border-width: 1px;

/* Inputs (text, select, etc.) */
--jp-widgets-input-color: var(--vscode-input-foreground, var(--vscode-editor-foreground));
/* Default to notebook cell editor bgcolor; dark theme overrides below */
--jp-widgets-input-background-color: var(--vscode-notebook-cellEditorBackground, var(--vscode-editor-background));
--jp-widgets-input-border-color: var(--vscode-input-border,
var(--vscode-panel-border, var(--vscode-editorPane-border)));
--jp-widgets-input-focus-border-color: var(--vscode-focusBorder);

/* Readout text (e.g., slider value) */
--jp-widgets-readout-color: var(--vscode-editor-foreground);

/* Links */
--jp-content-link-color: var(--vscode-textLink-foreground);

/* Borders */
--jp-border-color1: var(--vscode-panel-border, var(--vscode-editorPane-border));
--jp-border-color2: var(--vscode-panel-border, var(--vscode-editorPane-border));

/* Widgets */
--jp-widgets-color: var(--vscode-editor-foreground);
--jp-widgets-label-color: var(--vscode-editor-foreground);
--jp-widgets-font-size: var(--vscode-editor-font-size, 13px);
}

/* Light theme specifics */
.vscode-light .cell-output-ipywidget-background {
/* In light theme, prefer the cell editor background to match notebook cells */
--jp-layout-color0: var(--vscode-notebook-cellEditorBackground);
--jp-layout-color1: var(--vscode-notebook-cellEditorBackground);
--jp-layout-color2: var(--vscode-notebook-cellEditorBackground);
--jp-layout-color3: var(--vscode-notebook-cellEditorBackground);
--jp-widgets-input-background-color: var(--vscode-notebook-cellEditorBackground);
}

/* Dark theme specifics */
.vscode-dark .cell-output-ipywidget-background {
/* In dark theme, use input background which aligns well with controls */
--jp-layout-color0: var(--vscode-input-background);
--jp-layout-color1: var(--vscode-input-background);
--jp-layout-color2: var(--vscode-input-background);
--jp-layout-color3: var(--vscode-input-background);
--jp-widgets-input-background-color: var(--vscode-input-background);
}

/* High contrast adjustments */
.vscode-high-contrast .cell-output-ipywidget-background {
--jp-border-color1: var(--vscode-contrastActiveBorder, var(--vscode-contrastBorder));
--jp-border-color2: var(--vscode-contrastBorder, var(--vscode-contrastActiveBorder));
}
3 changes: 2 additions & 1 deletion src/webviews/webview-side/ipywidgets/renderer/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.cell-output-ipywidget-background {
background: white !important;
/* Let the container inherit theme colors; bridge will set JupyterLab vars */
background: transparent !important;
}
Loading