Skip to content
Merged
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
118 changes: 118 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,124 @@
/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS
Included with MyST-NB until removal in v1.4.0 (Mar 2026).
*/
div.cell_output table {
border: none;
border-collapse: collapse;
border-spacing: 0;
color: var(--color-foreground-primary);
font-size: 1em;
table-layout: fixed;
}
div.cell_output thead {
border-bottom: 1px solid var(--color-table-border);
vertical-align: bottom;
}
div.cell_output tr,
div.cell_output th,
div.cell_output td {
text-align: right;
vertical-align: middle;
padding: 0.5em 0.5em;
line-height: normal;
white-space: normal;
max-width: none;
border: none;
}
div.cell_output th {
font-weight: bold;
/* background: var(--color-table-header-background); */
}
div.cell_output tbody tr:nth-child(odd) {
background: var(--color-background-secondary);
}
div.cell_output tbody tr:hover {
background: var(--color-background-hover);
}

/* ipywidgets dark mode support.
The widgets CSS uses --jp-* variables throughout, but Furo doesn't define them.
The embed bundle injects the --jp-* defaults in :root *after* page load, so
remapping them is unreliable. Instead we directly override the widget selectors
with Furo's --color-* variables, which already adapt to light/dark mode.
noUiSlider (used for sliders) has entirely hardcoded colors, so those are
also overridden here.
*/

/* Text / label colors */
.jupyter-widgets,
.widget-label, .jupyter-widget-label,
.widget-label-basic, .jupyter-widget-label-basic,
.widget-readout, .jupyter-widget-readout {
color: var(--color-foreground-primary) !important;
}

/* Input / textarea / select backgrounds */
.jupyter-widgets input[type="text"],
.jupyter-widgets input[type="number"],
.jupyter-widgets input[type="password"],
.jupyter-widgets textarea,
.jupyter-widgets select {
background-color: var(--color-background-secondary) !important;
color: var(--color-foreground-primary) !important;
border-color: var(--color-background-border) !important;
}

/* Checkboxes: use brand color for the check/tick instead of bright white/blue */
.jupyter-widgets input[type="checkbox"] {
accent-color: var(--color-brand-primary) !important;
}

/* Checkbox body: render native checkbox in dark style in dark mode */
body[data-theme="dark"] .jupyter-widgets input[type="checkbox"] {
color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) .jupyter-widgets input[type="checkbox"] {
color-scheme: dark;
}
}

/* Color picker: tone down the bright border */
.jupyter-widgets input[type="color"] {
background-color: var(--color-background-secondary) !important;
border-color: var(--color-background-border) !important;
}

/* Buttons */
.jupyter-button {
background-color: var(--color-background-secondary) !important;
color: var(--color-foreground-primary) !important;
border-color: var(--color-background-border) !important;
}
.jupyter-button.mod-primary {
background-color: var(--color-brand-primary) !important;
color: white !important;
}

/* noUiSlider track, handle, and active range */
.widget-slider .noUi-target,
.jupyter-widget-slider .noUi-target {
background: var(--color-background-secondary) !important;
border-color: var(--color-background-border) !important;
box-shadow: none !important;
}
.widget-slider .noUi-handle,
.jupyter-widget-slider .noUi-handle {
background: var(--color-background-primary) !important;
border-color: var(--color-background-border) !important;
box-shadow: none !important;
}
.widget-slider .noUi-connect,
.jupyter-widget-slider .noUi-connect {
background: var(--color-brand-primary) !important;
}

/* Misc. overrides */

table.dataframe {
font-size: 0.8em !important;
}

figcaption p {
font-size: 85%;
line-height: 1.3;
Expand Down
8 changes: 8 additions & 0 deletions docs/changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release notes

## v0.1.3 (unreleased)

* Improve styling of abcjs containers, pandas dataframes, and ipywidgets
in the docs ({pull}`103`).
You can now activate abcjs responsive mode in
{class}`~pyabc2.abcjs.widget.ABCJSWidget`,
but non-responsive is still the default.

## v0.1.2 (2026-02-03)

* Update Norbeck to the current 2026-01 version ({pull}`96`)
Expand Down
14 changes: 14 additions & 0 deletions pyabc2/abcjs/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ class ABCJSWidget(anywidget.AnyWidget):
740,
help="Width of the staff in pixels.",
).tag(sync=True)
responsive = traitlets.Bool(
False,
help=(
"Whether the rendering should be responsive to container width "
"(up to `staff_width` + some padding)."
),
).tag(sync=True)
transpose = traitlets.Integer(
0,
help="Visual transpose in half steps.",
Expand Down Expand Up @@ -115,6 +122,11 @@ def interactive(abc: str = "", **kwargs) -> "ipywidgets.Widget": # pragma: no c
description="Staff width (px)",
**slider_kws,
)
responsive_cbox = ipw.Checkbox(
value=False,
description="Responsive",
indent=True,
)
line_thickness_slider = ipw.FloatSlider(
min=-0.4,
max=2,
Expand Down Expand Up @@ -152,6 +164,7 @@ def interactive(abc: str = "", **kwargs) -> "ipywidgets.Widget": # pragma: no c

ipw.link((w, "abc"), (input_box, "value"))
ipw.link((w, "staff_width"), (width_slider, "value"))
ipw.link((w, "responsive"), (responsive_cbox, "value"))
ipw.link((w, "scale"), (scale_slider, "value"))
ipw.link((w, "line_thickness_increase"), (line_thickness_slider, "value"))
ipw.link((w, "transpose"), (transpose_slider, "value"))
Expand Down Expand Up @@ -196,6 +209,7 @@ def save(_):
[
input_box,
width_slider,
responsive_cbox,
scale_slider,
line_thickness_slider,
transpose_slider,
Expand Down
17 changes: 14 additions & 3 deletions pyabc2/abcjs/widget/index.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
div.container.debug {
div.abcjs-widget-container {
max-width: var(--staff-max-width, none);
overflow-x: auto;
}

div.abcjs-widget-container.debug {
color: forestgreen;
border: 1px solid forestgreen;
}

div.music.debug {
div.abcjs-widget-container div.music {
/* ABCJS seems to set `overflow: hidden` on the music div,
which prevents the container's auto overflow from working. */
overflow: visible !important;
}

div.abcjs-widget-container div.music.debug {
border: 1px dashed grey;
}

div.container code {
div.abcjs-widget-container code {
white-space: pre-wrap;
}
23 changes: 22 additions & 1 deletion pyabc2/abcjs/widget/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ function render({ model, el }) {
let showDebugInput = () => model.get('debug_input');
let showLogo = () => model.get('logo');
let staffwidth = () => model.get('staff_width');
let doResize = () => model.get('responsive');
let visualTranspose = () => model.get('transpose');

let active_music_ids = model.get("_active_music_ids");
let first_load = model.get("_first_load");
console.log(`first_load ${first_load}`);

let container = el;
container.classList.add('container');
container.classList.add('abcjs-widget-container');

if ((first_load || showLogo()) && !hide()) {
let logo = document.createElement('img');
Expand Down Expand Up @@ -94,6 +95,12 @@ function render({ model, el }) {
music.innerHTML = '';
};

// Clear any inline styles ABCJS may have been set on the music div
// (e.g. height/position from responsive mode's ResizeObserver),
// which would otherwise persist across re-renders and cause
// phantom empty space when toggling responsive on/off.
music.removeAttribute('style');

head.innerHTML = '';
if (showDebugInput() && !hide()) {
let code = document.createElement('code');
Expand All @@ -113,6 +120,19 @@ function render({ model, el }) {
if (showDebugBox()) {showDebug.push('box')};
if (showDebugGrid()) {showDebug.push('grid')};

// Responsive setting
let responsive = doResize() ? "resize" : undefined;
// We drive max-width via a CSS custom property so ABCJS's async ResizeObserver
// (which may clear inline max-width) cannot override it.
// ABCJS docs say left and right padding in the SVG are 15 and 50,
// so we try to give enough space for that, but note that in practice
// the results are not identical to responsive-off.
if (doResize()) {
container.style.setProperty('--staff-max-width', (staffwidth() + 65) + 'px');
} else {
container.style.removeProperty('--staff-max-width');
}

// NOTE: doesn't work with `music_id` passed as target,
// even though it should, still not sure why
let tunes = ABCJS.renderAbc(
Expand All @@ -121,6 +141,7 @@ function render({ model, el }) {
{
foregroundColor: foregroundColor(),
lineThickness: lineThickness(),
responsive: responsive,
scale: scale(),
showDebug: showDebug,
staffwidth: staffwidth(),
Expand Down