Skip to content

global styling and nonvisualsยค

    from nbconvert_a11y.table import new
%%
    class GlobalStyle(traitlets.HasTraits):
each notebook represents a new figure/ground relationship with unique styles.
the `GlobalStyle` provides a single display to handle generic css we may want to apply
or visually hiding elements. a benefit of this technique is that styles will be saved with the notebook.
unfortunuately, this technique will fail in `nbconvert`.

        NONVISUAL = """{
            clip: rect(0 0 0 0);
            clip-path: inset(50%);
            height: 1px;
            overflow: hidden;
            position: absolute;
            white-space: nowrap;
            width: 1px;
        }"""

        style = traitlets.Instance(bs4.Tag, (), dict(name="style"))
        nonvisual_selectors = traitlets.List([".nonvisual", ".visually-hidden", ".nv"])
        css = traitlets.Unicode()
        style_display = traitlets.Instance(IPython.display.DisplayHandle, (), {})
        nonvisual_display = traitlets.Instance(IPython.display.DisplayHandle, (), {})

        def nonvisual(self, *visually_hidden):
updating nonvisual traits in annoying. basically, you pile selectors on the `NONVISUAL`
styles. theres no way to define variables for selectors or styles so this is what we are left with.
sure we could use `display: none;`, but that would be visually biased and this way we can retain the screen reader experience.

            for visually_hidden in visually_hidden: 
                self.nonvisual_selectors.extend(map(str.strip, visually_hidden.split(",")))

            return self

        def visual(self, *visually_shown):
            for visually_shown in visually_shown:
                for key in map(str.strip, visually_hidden.split(",")):
                    try:
                        self.nonvisual_selectors.remove(key)
                    except ValueError:
                        pass

        def update_nonvisual_display(self):
            self.nonvisual_display.update(new("style", self.format_nonvisual()))

        def format_nonvisual(self):
            return " ".join((", ".join(self.nonvisual_selectors), self.NONVISUAL))

        @traitlets.observe("css")
        def update_display(self, change):
            self.style.string = self.css
            self.style_display.update(HTML(self.style))

        def __add__(self, b):
            self.css = "\n".join((self.css, b))
            return self

        add =  new = __iadd__ = __radd__ = __add__

        def _ipython_display_(self):
            self.style_display.display(HTML(self.style))
            self.nonvisual_display.display(HTML(new("style", self.format_nonvisual())))
class GlobalStyle(traitlets.HasTraits):

each notebook represents a new figure/ground relationship with unique styles. the GlobalStyle provides a single display to handle generic css we may want to apply or visually hiding elements. a benefit of this technique is that styles will be saved with the notebook. unfortunuately, this technique will fail in nbconvert.

    NONVISUAL = """{
        clip: rect(0 0 0 0);
        clip-path: inset(50%);
        height: 1px;
        overflow: hidden;
        position: absolute;
        white-space: nowrap;
        width: 1px;
    }"""

    style = traitlets.Instance(bs4.Tag, (), dict(name="style"))
    nonvisual_selectors = traitlets.List([".nonvisual", ".visually-hidden", ".nv"])
    css = traitlets.Unicode()
    style_display = traitlets.Instance(IPython.display.DisplayHandle, (), {})
    nonvisual_display = traitlets.Instance(IPython.display.DisplayHandle, (), {})

    def nonvisual(self, *visually_hidden):

updating nonvisual traits in annoying. basically, you pile selectors on the NONVISUAL styles. theres no way to define variables for selectors or styles so this is what we are left with. sure we could use display: none;, but that would be visually biased and this way we can retain the screen reader experience.

        for visually_hidden in visually_hidden: 
            self.nonvisual_selectors.extend(map(str.strip, visually_hidden.split(",")))

        return self

    def visual(self, *visually_shown):
        for visually_shown in visually_shown:
            for key in map(str.strip, visually_hidden.split(",")):
                try:
                    self.nonvisual_selectors.remove(key)
                except ValueError:
                    pass

    def update_nonvisual_display(self):
        self.nonvisual_display.update(new("style", self.format_nonvisual()))

    def format_nonvisual(self):
        return " ".join((", ".join(self.nonvisual_selectors), self.NONVISUAL))

    @traitlets.observe("css")
    def update_display(self, change):
        self.style.string = self.css
        self.style_display.update(HTML(self.style))

    def __add__(self, b):
        self.css = "\n".join((self.css, b))
        return self

    add =  new = __iadd__ = __radd__ = __add__

    def _ipython_display_(self):
        self.style_display.display(HTML(self.style))
        self.nonvisual_display.display(HTML(new("style", self.format_nonvisual())))
%%
    display(style := GlobalStyle())
    style.nonvisual(".jp-InputPrompt", ".jp-OutputArea-promptOverlay")
    style += \
```css
.lm-TabBar,
[aria-label$="sidebar"], #jp-menu-panel, #jp-top-panel, 
#jp-main-statusbar,
[aria-label="notebook actions"] > :not([data-jp-item-name="executionProgress"]) {
    opacity: 0;
    transition: all linear .5s;
    filter: grayscale(1);
    &:focus-within, &:hover {
        opacity: 1;
        filter: grayscale(0);
    }
}

[aria-label="notebook actions"] {
    &:focus-within, &:hover {
        opacity: 1;
        filter: grayscale(0);
    }
}

#jp-MainLogo {
    svg {
        display: none;
    }
    &::before {
        content: "๐Ÿ–•๐Ÿ–•";
    }
}
.jp-Cell-outputWrapper, .cm-line {
    filter: grayscale(1);
    &:focus-within, &:hover, &.cm-activeLine {
        transition: all linear .5s;
        filter: grayscale(0);
    }
}
```
display(style := GlobalStyle())
style.nonvisual(".jp-InputPrompt", ".jp-OutputArea-promptOverlay")
style += \
.lm-TabBar,
[aria-label$="sidebar"], #jp-menu-panel, #jp-top-panel, 
#jp-main-statusbar,
[aria-label="notebook actions"] > :not([data-jp-item-name="executionProgress"]) {
    opacity: 0;
    transition: all linear .5s;
    filter: grayscale(1);
    &:focus-within, &:hover {
        opacity: 1;
        filter: grayscale(0);
    }
}

[aria-label="notebook actions"] {
    &:focus-within, &:hover {
        opacity: 1;
        filter: grayscale(0);
    }
}

#jp-MainLogo {
    svg {
        display: none;
    }
    &::before {
        content: "๐Ÿ–•๐Ÿ–•";
    }
}
.jp-Cell-outputWrapper, .cm-line {
    filter: grayscale(1);
    &:focus-within, &:hover, &.cm-activeLine {
        transition: all linear .5s;
        filter: grayscale(0);
    }
}