Refactored CSS, code annotations almost working properly

This commit is contained in:
squidfunk
2021-11-21 17:45:49 +01:00
parent 88ba609ee1
commit fa9f906c2b
157 changed files with 17406 additions and 504 deletions

View File

@@ -47,6 +47,8 @@ export function resetFocusable(
el.removeAttribute("tabindex")
}
/* ------------------------------------------------------------------------- */
/**
* Set scroll lock
*

View File

@@ -25,7 +25,6 @@ import {
NEVER,
Observable,
Subject,
combineLatestWith,
distinctUntilKeyChanged,
finalize,
fromEvent,
@@ -34,10 +33,23 @@ import {
mergeWith,
of,
switchMap,
take,
takeWhile,
tap,
withLatestFrom
withLatestFrom,
distinctUntilChanged,
takeWhile,
repeat,
EMPTY,
observeOn,
debounceTime,
mapTo,
throttle,
share,
defer,
switchMapTo,
shareReplay,
combineLatest,
asapScheduler,
animationFrameScheduler
} from "rxjs"
import { feature } from "~/_"
@@ -52,7 +64,11 @@ import {
getElementSize,
getElements,
getOptionalElement,
watchMedia
watchMedia,
watchElementContentOffset,
watchElementVisibility,
getElementOffset,
watchElementOffset
} from "~/browser"
import {
renderAnnotation,
@@ -69,7 +85,7 @@ import { Component } from "../../_"
* Code block
*/
export interface CodeBlock {
scroll: boolean /* Code block overflows */
scrollable: boolean /* Code block overflows */
annotations?: HTMLElement[] /* Code block annotations */
}
@@ -82,7 +98,7 @@ export interface CodeBlock {
*/
interface WatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */
print$: Observable<boolean> /* Print observable */
print$: Observable<boolean> /* Media print observable */
}
/**
@@ -90,7 +106,8 @@ interface WatchOptions {
*/
interface MountOptions {
viewport$: Observable<Viewport> /* Viewport observable */
print$: Observable<boolean> /* Print observable */
hover$: Observable<boolean> /* Media hover observable */
print$: Observable<boolean> /* Media print observable */
}
/* ----------------------------------------------------------------------------
@@ -102,6 +119,32 @@ interface MountOptions {
*/
let index = 0
/* ----------------------------------------------------------------------------
* Helper functions
* ------------------------------------------------------------------------- */
/**
* Find the code annotations belonging to a code block
*
* @param el - Code block element
*
* @returns Code annotations or nothing
*/
function findAnnotationsList(el: HTMLElement): HTMLElement | undefined {
if (el.nextElementSibling) {
const sibling = el.nextElementSibling as HTMLElement
if (sibling.tagName === "OL")
return sibling
/* Paragraph, may be empty, see https://bit.ly/3r4ZJ2O */
else if (sibling.tagName === "P" && !sibling.children.length)
return findAnnotationsList(sibling)
}
/* Everything else */
return undefined
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
@@ -120,85 +163,168 @@ let index = 0
export function watchCodeBlock(
el: HTMLElement, { viewport$, print$ }: WatchOptions
): Observable<CodeBlock> {
const container$ = of(el)
/* Watch content tabs as they can reveal code blocks */
const reveal$ = defer(() => {
const container = el.closest("[data-tabs]")
if (container instanceof HTMLElement) {
return merge(
...getElements(":scope > input", container)
.map(input => fromEvent(input, "change"))
)
}
return NEVER
})
/* Compute overflow state on resize and content tab change */
const scroll$ = viewport$
.pipe(
switchMap(child => {
const container = child.closest("[data-tabs]")
if (container instanceof HTMLElement) {
return merge(
...getElements(":scope > input", container)
.map(input => fromEvent(input, "change"))
)
}
return NEVER
})
distinctUntilKeyChanged("size"),
mergeWith(reveal$),
map(() => {
const visible = getElementSize(el)
const content = getElementContentSize(el)
return content.width > visible.width
}),
distinctUntilChanged()
)
/* Transform annotations */
const annotations: HTMLElement[] = []
const container =
el.closest(".highlighttable") ||
el.closest(".highlight")
if (container) {
const list = container.nextElementSibling
if (list instanceof HTMLOListElement && (
container.classList.contains("annotate") ||
feature("content.code.annotate")
)) {
const items = Array.from(list.children)
list.remove()
/* Compute content offset of code block */
const offset$ = watchElementContentOffset(el)
// scroll$
// .pipe(
// switchMap(scrollable => scrollable
// ? watchElementContentOffset(el)
// : EMPTY
// ),
// shareReplay(1) // TBD
// )
/* Replace comments with annotations */
for (const comment of getElements(".c, .c1, .cm", el)) {
// TODO: from here, annotations #############################################
/* Split comment at annotations */ // TODO: refactor when revisiting annotations
let match: RegExpExecArray | null
let text = comment.firstChild as Text
do {
match = /\((\d+)\)/.exec(text.textContent!)
if (match && match.index) {
const bubble = text.splitText(match.index)
text = bubble.splitText(match[0].length) // complete match length
/* Compute whether code annotations must be temporarily hidden */
const finish$ = offset$.pipe(debounceTime(125), mapTo(false))
const hidden$ = merge(
finish$,
offset$.pipe(throttle(() => finish$), mapTo(true))
)
const [, j = -1] = match
const content = items[+j - 1]
if (typeof content !== "undefined") {
const annotation = renderAnnotation(+j, content.childNodes)
bubble.replaceWith(annotation)
annotations.push(annotation)
}
}
} while (match)
}
/* Move elements back on print */ // TODO: refactor memleak (instant loading)
print$.subscribe(active => {
if (active) {
container.insertAdjacentElement("afterend", list)
for (const annotation of annotations) {
const id = parseInt(annotation.getAttribute("data-index")!, 10)
const typeset = getOptionalElement(":scope .md-typeset", annotation)!
items[id - 1].append(...Array.from(typeset.childNodes))
}
} else {
list.remove()
for (const annotation of annotations) {
const id = parseInt(annotation.getAttribute("data-index")!, 10)
const nodes = items[id - 1].childNodes
getElement(":scope .md-typeset", annotation)
.append(...Array.from(nodes))
}
}
})
/* Hide tooltip while scrolling */
hidden$.subscribe(hidden => {
if (hidden) {
console.log("scrolling...")
// el.setAttribute("data-md-state", "scroll")
} else {
console.log("scrolling done")
el.removeAttribute("data-md-state")
}
}
})
const annotations$ = defer(() => {
const annotations: HTMLElement[] = []
/* */
const container = el.closest(".highlighttable") || el.closest(".highlight")
if (!(container instanceof HTMLElement))
return NEVER
/* */
if (container instanceof HTMLElement) {
const list = findAnnotationsList(container)
if (typeof list !== "undefined" && (
container.classList.contains("annotate") ||
feature("content.code.annotate")
)) {
/* Replace comments with annotations */
const items = getElements(":scope > li", list)
for (const comment of getElements(".c, .c1, .cm", el)) {
/* Split comment at annotations */ // TODO: refactor when revisiting annotations
let match: RegExpExecArray | null
let text = comment.firstChild as Text
do {
match = /\((\d+)\)/.exec(text.textContent!)
if (match && match.index) {
const bubble = text.splitText(match.index)
text = bubble.splitText(match[0].length) // complete match length
const [, j = -1] = match
const content = items[+j - 1]
if (typeof content !== "undefined") {
const annotation = renderAnnotation(+j, content.childNodes)
bubble.replaceWith(annotation) // bubble is and will stay replaced...
annotations.push(annotation)
}
}
} while (match)
}
}
}
// // offset, compute tooltip position...
// offset$.subscribe(({ x }) => {
// el.style.setProperty("--md-tooltip-x", `${x}px`)
// })
// wrong, we must always recompute the position..
for (const annotation of annotations) {
const size = getElementSize(annotation)
offset$
.pipe(
// observeOn(animationFrameScheduler), // TODO: ?
withLatestFrom(watchElementOffset(annotation)),
map(([scroll, offset]) => {
// if left is clamped, we must add to top!
// TODO: this must also be placed here!
annotation.style.setProperty(
"--md-tooltip-x", `${offset.x - scroll.x}px`
)
annotation.style.setProperty(
"--md-tooltip-y", `${offset.y - scroll.y}px`
)
})
)
.subscribe()
}
return of(annotations)
})
// // /* Move elements back on print */ // TODO: refactor memleak (instant loading)
// // print$.subscribe(active => {
// // if (active) {
// // container.insertAdjacentElement("afterend", list)
// // for (const annotation of annotations) {
// // const id = parseInt(annotation.getAttribute("data-index")!, 10)
// // const typeset = getOptionalElement(":scope .md-typeset", annotation)!
// // items[id - 1].append(...Array.from(typeset.childNodes))
// // }
// // } else {
// // list.remove()
// // for (const annotation of annotations) {
// // const id = parseInt(annotation.getAttribute("data-index")!, 10)
// // const nodes = items[id - 1].childNodes
// // getElement(":scope .md-typeset", annotation)
// // .append(...Array.from(nodes))
// // }
// // }
// // })
// }
// }
/* Check overflow on resize and tab change */
return viewport$
.pipe(
distinctUntilKeyChanged("size"),
mergeWith(container$),
map(() => {
mergeWith(reveal$),
switchMapTo(annotations$),
map(annotations => {
const visible = getElementSize(el)
const content = getElementContentSize(el)
return {
@@ -222,43 +348,55 @@ export function watchCodeBlock(
* @returns Code block component observable
*/
export function mountCodeBlock(
el: HTMLElement, options: MountOptions
el: HTMLElement, { hover$, ...options }: MountOptions
): Observable<Component<CodeBlock>> {
const internal$ = new Subject<CodeBlock>()
internal$
.pipe(
withLatestFrom(watchMedia("(hover)"))
withLatestFrom(hover$)
)
.subscribe(([{ scroll }, hover]) => {
.subscribe(([{ scrollable: scroll }, hover]) => {
if (scroll && hover)
setFocusable(el)
else
resetFocusable(el)
})
/* Compute annotation position */
internal$
.pipe(
take(1),
takeWhile(({ annotations }) => !!annotations?.length),
map(({ annotations }) => annotations!
.map(annotation => getElement(".md-tooltip", annotation))
),
combineLatestWith(viewport$
.pipe(
distinctUntilKeyChanged("size")
)
)
)
.subscribe(([tooltips, { size }]) => {
for (const tooltip of tooltips) {
const { x, width } = tooltip.getBoundingClientRect()
if (x + width > size.width)
tooltip.classList.add("md-tooltip--end")
else
tooltip.classList.remove("md-tooltip--end")
}
})
// /* Compute annotation position */
// internal$
// .pipe(
// take(1),
// takeWhile(({ annotations }) => !!annotations?.length),
// combineLatestWith(viewport$
// .pipe(
// distinctUntilKeyChanged("size")
// )
// )
// )
// .subscribe(([{ annotations }, { size }]) => {
// if (typeof annotations === "undefined")
// return
// /* Compute annotation positions */
// for (const annotation of annotations) {
// const annotationIndex = getElement(".md-annotation__index", annotation)
// const tooltip = getElement(".md-tooltip", annotation)
// console.log(annotationIndex, tooltip)
// const { x } = getElementOffset(annotationIndex)
// console.log(x)
// }
// // console.log(tooltips, size)
// // for (const tooltip of tooltips) {
// // const { x, width } = tooltip.getBoundingClientRect()
// // if (x + width > size.width)
// // tooltip.classList.add("md-tooltip--end")
// // else
// // tooltip.classList.remove("md-tooltip--end")
// // }
// })
/* Render button for Clipboard.js integration */
if (ClipboardJS.isSupported()) {

View File

@@ -39,7 +39,7 @@ export function renderAnnotation(
): HTMLElement {
return (
<aside class="md-annotation" data-index={id} tabIndex={0}>
<div class="md-tooltip">
<div class="md-annotation__inner md-tooltip">
<div class="md-tooltip__inner md-typeset">
{...Array.from(content)}
</div>

View File

@@ -46,9 +46,9 @@
@import "main/layout/clipboard";
@import "main/layout/content";
@import "main/layout/dialog";
@import "main/layout/footer";
@import "main/layout/form";
@import "main/layout/header";
@import "main/layout/footer";
@import "main/layout/nav";
@import "main/layout/search";
@import "main/layout/select";

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Color definitions
// Color variables
:root {
// Default color shades

View File

@@ -61,7 +61,7 @@ kbd {
// Rules: typesetted content
// ----------------------------------------------------------------------------
// Icon definitions
// General variables
:root {
--md-typeset-table-sort-icon: svg-load("material/sort.svg");
--md-typeset-table-sort-icon--asc: svg-load("material/sort-ascending.svg");
@@ -84,12 +84,7 @@ kbd {
}
// Default spacing
ul,
ol,
dl,
figure,
blockquote,
pre {
:is(ul, ol, dl, figure, blockquote, pre) {
margin: 1em 0;
}
@@ -134,8 +129,7 @@ kbd {
}
// Headline on level 5-6
h5,
h6 {
:is(h5, h6) {
margin: px2em(16px, 12.8px) 0;
color: var(--md-default-fg-color--light);
font-weight: 700;
@@ -167,8 +161,7 @@ kbd {
}
// Text link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}
@@ -180,9 +173,7 @@ kbd {
}
// Code block
code,
pre,
kbd {
:is(code, pre, kbd) {
color: var(--md-code-fg-color);
direction: ltr;
@@ -209,12 +200,7 @@ kbd {
}
// Code block in headline
h1 code,
h2 code,
h3 code,
h4 code,
h5 code,
h6 code {
:is(h1, h2, h3, h4, h5, h6) code {
margin: initial;
padding: initial;
background-color: transparent;
@@ -239,6 +225,7 @@ kbd {
padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);
overflow: auto;
word-break: normal;
outline-color: var(--md-accent-fg-color);
box-shadow: none;
box-decoration-break: slice;
touch-action: auto;
@@ -268,20 +255,6 @@ kbd {
}
}
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Unformatted text
> pre {
margin: 1em px2rem(-16px);
// Code block
code {
border-radius: 0;
}
}
}
// Keyboard key
kbd {
display: inline-block;
@@ -317,8 +290,7 @@ kbd {
position: relative;
// Tooltip
&[title]:focus::after,
&[title]:hover::after {
&[title]:is(:focus, :hover)::after {
@include z-depth(2);
position: absolute;
@@ -344,8 +316,7 @@ kbd {
}
// Superscript and subscript
sup,
sub {
:is(sup, sub) {
margin-left: px2em(1px, 12.8px);
// Adjust for right-to-left languages
@@ -376,8 +347,7 @@ kbd {
}
// Unordered and ordered list
ul,
ol {
:is(ul, ol) {
display: flow-root;
margin-left: px2em(10px);
padding: 0;
@@ -410,8 +380,7 @@ kbd {
}
// Adjust spacing
p,
blockquote {
:is(p, blockquote) {
margin: 0.5em 0;
}
@@ -421,8 +390,7 @@ kbd {
}
// Nested list
ul,
ol {
:is(ul, ol) {
margin: 0.5em 0 0.5em px2em(10px);
// Adjust for right-to-left languages
@@ -446,8 +414,7 @@ kbd {
}
// Image or icon
img,
svg {
:is(img, svg) {
max-width: 100%;
height: auto;
@@ -519,8 +486,7 @@ kbd {
}
// Elements in table heading and cell
th > *,
td > * {
:is(th, td) > * {
// Adjust spacing on first child
&:first-child {
@@ -534,8 +500,7 @@ kbd {
}
// Table heading and cell
th:not([align]),
td:not([align]) {
:is(th, td):not([align]) {
text-align: left;
// Adjust for right-to-left languages
@@ -644,3 +609,21 @@ kbd {
}
}
}
// ----------------------------------------------------------------------------
// Rules: top-level
// ----------------------------------------------------------------------------
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Top-level unformatted content
.md-content__inner > pre {
margin: 1em px2rem(-16px);
// Code block
code {
border-radius: 0;
}
}
}

View File

@@ -47,7 +47,7 @@ $admonitions: (
// Rules: layout
// ----------------------------------------------------------------------------
// Icon definitions
// Admonition variables
:root {
@each $names, $props in $admonitions {
--md-admonition-icon--#{nth($names, 1)}:
@@ -60,8 +60,8 @@ $admonitions: (
// Scoped in typesetted content to match specificity of regular content
.md-typeset {
// Admonition
.admonition {
// Admonition and Details
:is(.admonition, details) {
display: flow-root;
margin: px2em(20px, 12.8px) 0;
padding: 0 px2rem(12px);
@@ -87,7 +87,7 @@ $admonitions: (
}
// Adjust vertical spacing for nested admonitions
.admonition {
:is(.admonition, details) {
margin-top: 1em;
margin-bottom: 1em;
}
@@ -114,7 +114,7 @@ $admonitions: (
}
// Admonition title
.admonition-title {
:is(.admonition-title, summary) {
position: relative;
margin: 0 px2rem(-12px) 0 px2rem(-16px);
padding: px2rem(8px) px2rem(12px) px2rem(8px) px2rem(40px);
@@ -165,13 +165,19 @@ $admonitions: (
$name: list.nth($names, 1);
$tint: list.nth($props, 2);
// Admonition flavour selectors
$flavours: ();
@each $name in $names {
$flavours: list.join($flavours, ".#{$name}", $separator: comma);
}
// Admonition flavour
.md-typeset .admonition.#{$name} {
.md-typeset :is(.admonition, details):is(#{$flavours}) {
border-color: $tint;
}
// Admonition flavour title
.md-typeset .#{$name} > .admonition-title {
.md-typeset :is(#{$flavours}) > :is(.admonition-title, summary) {
background-color: color.adjust($tint, $alpha: -0.9);
border-color: $tint;
@@ -183,13 +189,4 @@ $admonitions: (
mask-size: contain;
}
}
// Define synonyms for flavours
@if length($names) > 1 {
@for $n from 2 through length($names) {
.#{nth($names, $n)} {
@extend .#{$name};
}
}
}
}

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Footnotes variables
:root {
--md-footnotes-icon: svg-load("material/keyboard-return.svg");
}
@@ -61,8 +61,7 @@
}
// Show backreferences on footnote hover/target
&:hover .footnote-backref,
&:target .footnote-backref {
&:is(:hover, :target) .footnote-backref {
transform: translateX(0);
opacity: 1;
}

View File

@@ -50,8 +50,7 @@
}
// Show headerlinks on parent hover
:hover > .headerlink,
:target > .headerlink,
:is(:hover, :target) > .headerlink,
.headerlink:focus {
opacity: 1;
transition:
@@ -61,8 +60,7 @@
// Adjust color on parent target or focus/hover
:target > .headerlink,
.headerlink:focus,
.headerlink:hover {
.headerlink:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}
@@ -84,9 +82,7 @@
}
// Adjust scroll offset for headlines of level 1-3
h1:target,
h2:target,
h3:target {
:is(h1, h2, h3):target {
scroll-margin-top: initial;
// Anchor correction hack
@@ -142,8 +138,7 @@
}
// Adjust scroll offset for headlines of level 5-6
h5:target,
h6:target {
:is(h5, h6):target {
scroll-margin-top: initial;
// Anchor correction hack

View File

@@ -28,9 +28,7 @@
.md-typeset {
// Deletion, addition or comment
del.critic,
ins.critic,
.critic.comment {
:is(del, ins, .comment).critic {
box-decoration-break: clone;
}

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Details variables
:root {
--md-details-icon: svg-load("material/chevron-right.svg");
}
@@ -36,8 +36,6 @@
// Details
details {
@extend .admonition;
display: flow-root;
padding-top: 0;
overflow: visible;
@@ -64,8 +62,6 @@
// Details title
summary {
@extend .admonition-title;
display: block;
min-height: px2rem(20px);
padding: px2rem(8px) px2rem(36px) px2rem(8px) px2rem(40px);
@@ -114,8 +110,7 @@
}
// Hide native details marker
&::marker,
&::-webkit-details-marker {
&:is(::marker, ::-webkit-details-marker) {
display: none;
}
}

View File

@@ -28,9 +28,7 @@
.md-typeset {
// Emoji and icon container
.emojione,
.twemoji,
.gemoji {
:is(.emojione, .twemoji, .gemoji) {
display: inline-flex;
height: px2em(18px);
vertical-align: text-top;

View File

@@ -26,8 +26,10 @@
// Code block
.highlight {
.o, // Operator
.ow { // Operator, word
// .o = Operator
// .ow = Operator, word
:is(.o, .ow) {
color: var(--md-code-hl-operator-color);
}
@@ -35,100 +37,112 @@
color: var(--md-code-hl-punctuation-color);
}
.cpf, // Comment, preprocessor file
.l, // Literal
.s, // Literal, string
.sb, // Literal, string backticks
.sc, // Literal, string char
.s2, // Literal, string double
.si, // Literal, string interpol
.s1, // Literal, string single
.ss { // Literal, string symbol
// .cpf = Comment, preprocessor file
// .l = Literal
// .s = Literal, string
// .sb = Literal, string backticks
// .sc = Literal, string char
// .s2 = Literal, string double
// .si = Literal, string interpol
// .s1 = Literal, string single
// .ss = Literal, string symbol
:is(.cpf, .l, .s, .sb, .sc, .s2, .si, .s1, .ss) {
color: var(--md-code-hl-string-color);
}
.cp, // Comment, pre-processor
.se, // Literal, string escape
.sh, // Literal, string heredoc
.sr, // Literal, string regex
.sx { // Literal, string other
// .cp = Comment, pre-processor
// .se = Literal, string escape
// .sh = Literal, string heredoc
// .sr = Literal, string regex
// .sx = Literal, string other
:is(.cp, .se, .sh, .sr, .sx) {
color: var(--md-code-hl-special-color);
}
.m, // Number
.mb, // Number, binary
.mf, // Number, float
.mh, // Number, hex
.mi, // Number, integer
.il, // Number, integer long
.mo { // Number, octal
// .m = Number
// .mb = Number, binary
// .mf = Number, float
// .mh = Number, hex
// .mi = Number, integer
// .il = Number, integer long
// .mo = Number, octal
:is(.m, .mb, .mf, .mh, .mi, .il, .mo) {
color: var(--md-code-hl-number-color);
}
.k, // Keyword,
.kd, // Keyword, declaration
.kn, // Keyword, namespace
.kp, // Keyword, pseudo
.kr, // Keyword, reserved
.kt { // Keyword, type
// .k = Keyword,
// .kd = Keyword, declaration
// .kn = Keyword, namespace
// .kp = Keyword, pseudo
// .kr = Keyword, reserved
// .kt = Keyword, type
:is(.k, .kd, .kn, .kp, .kr, .kt) {
color: var(--md-code-hl-keyword-color);
}
.kc, // Keyword, constant
.n { // Name
// .kc = Keyword, constant
// .n = Name
:is(.kc, .n) {
color: var(--md-code-hl-name-color);
}
.no, // Name, constant
.nb, // Name, builtin
.bp { // Name, builtin pseudo
// .no = Name, constant
// .nb = Name, builtin
// .bp = Name, builtin pseudo
:is(.no, .nb, .bp) {
color: var(--md-code-hl-constant-color);
}
.nc, // Name, class
.ne, // Name, exception
.nf, // Name, function
.nn { // Name, namespace
// .nc = Name, class
// .ne = Name, exception
// .nf = Name, function
// .nn = Name, namespace
:is(.nc, .ne, .nf, .nn) {
color: var(--md-code-hl-function-color);
}
.nd, // Name, decorator
.ni, // Name, entity
.nl, // Name, label
.nt { // Name, tag
// .nd = Name, decorator
// .ni = Name, entity
// .nl = Name, label
// .nt = Name, tag
:is(.nd, .ni, .nl, .nt) {
color: var(--md-code-hl-keyword-color);
}
.c, // Comment
.cm, // Comment, multiline
.c1, // Comment, single
.ch, // Comment, shebang
.cs, // Comment, special
.sd { // Literal, string doc
// .c = Comment
// .cm = Comment, multiline
// .c1 = Comment, single
// .ch = Comment, shebang
// .cs = Comment, special
// .sd = Literal, string doc
:is(.c, .cm, .c1, .ch, .cs, .sd) {
color: var(--md-code-hl-comment-color);
}
.na, // Name, attribute
.nv, // Variable,
.vc, // Variable, class
.vg, // Variable, global
.vi { // Variable, instance
// .na = Name, attribute
// .nv = Variable,
// .vc = Variable, class
// .vg = Variable, global
// .vi = Variable, instance
:is(.na, .nv, .vc, .vg, .vi) {
color: var(--md-code-hl-variable-color);
}
.ge, // Generic, emph
.gr, // Generic, error
.gh, // Generic, heading
.go, // Generic, output
.gp, // Generic, prompt
.gs, // Generic, strong
.gu, // Generic, subheading
.gt { // Generic, traceback
// .ge = Generic, emph
// .gr = Generic, error
// .gh = Generic, heading
// .go = Generic, output
// .gp = Generic, prompt
// .gs = Generic, strong
// .gu = Generic, subheading
// .gt = Generic, traceback
:is(.ge, .gr, .gh, .go, .gp, .gs, .gu, .gt) {
color: var(--md-code-hl-generic-color);
}
.gd, // Diff, delete
.gi { // Diff, insert
// .gd = Diff, delete
// .gi = Diff, insert
:is(.gd, .gi) {
margin: 0 px2em(-2px);
padding: 0 px2em(2px);
border-radius: px2rem(2px);
@@ -198,8 +212,7 @@
// Set table elements to block layout, because otherwise the whole flexbox
// hacking won't work correctly
tbody,
td {
:is(tbody, td) {
display: block;
padding: 0;
}
@@ -270,36 +283,40 @@
border-bottom-right-radius: px2rem(2px);
}
}
}
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// ----------------------------------------------------------------------------
// Rules: top-level
// ----------------------------------------------------------------------------
// Top-level code block
&.md-content__inner > .highlight {
margin: 1em px2rem(-16px);
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Highlighted line
.hll {
margin: 0 px2rem(-16px);
padding: 0 px2rem(16px);
}
// Top-level code block
.md-content__inner > .highlight {
margin: 1em px2rem(-16px);
// Omit rounded borders
code {
border-radius: 0;
}
// Highlighted line
.hll {
margin: 0 px2rem(-16px);
padding: 0 px2rem(16px);
}
// Top-level code block with line numbers
&.md-content__inner > > .highlighttable {
margin: 1em px2rem(-16px);
// Omit rounded borders
code {
border-radius: 0;
}
}
// Highlighted line
.hll {
margin: 0 px2rem(-16px);
padding: 0 px2rem(16px);
}
// Top-level code block with line numbers
.md-content__inner > .highlighttable {
margin: 1em px2rem(-16px);
border-radius: 0;
// Highlighted line
.hll {
margin: 0 px2rem(-16px);
padding: 0 px2rem(16px);
}
}
}

View File

@@ -31,8 +31,7 @@
.keys {
// Keyboard key icon
kbd::before,
kbd::after {
kbd:is(::before, ::after) {
position: relative;
margin: 0;
color: inherit;

View File

@@ -129,39 +129,6 @@
}
}
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Top-level tabbed labels
> .tabbed-set .tabbed-labels {
max-width: 100vw;
margin: 0 px2rem(-16px);
padding-left: px2rem(16px);
scroll-padding-left: px2rem(16px);
// Adjust for right-to-left languages
[dir="rtl"] & {
padding-right: px2rem(16px);
padding-left: initial;
scroll-padding-right: px2rem(16px);
scroll-padding-left: initial;
}
// Hack: some browsers ignore the right padding on flex containers,
// see https://bit.ly/3lsPS3S
&::after {
padding-right: px2rem(16px);
content: "";
// Adjust for right-to-left languages
[dir="rtl"] & {
padding-right: initial;
padding-left: px2rem(16px);
}
}
}
}
// Tabbed content
.tabbed-content {
width: 100%;
@@ -228,6 +195,43 @@
}
}
// ----------------------------------------------------------------------------
// Rules: top-level
// ----------------------------------------------------------------------------
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Top-level tabbed labels
.md-content__inner > .tabbed-set .tabbed-labels {
max-width: 100vw;
margin: 0 px2rem(-16px);
padding-left: px2rem(16px);
scroll-padding-left: px2rem(16px);
// Adjust for right-to-left languages
[dir="rtl"] & {
padding-right: px2rem(16px);
padding-left: initial;
scroll-padding-right: px2rem(16px);
scroll-padding-left: initial;
}
// Hack: some browsers ignore the right padding on flex containers,
// see https://bit.ly/3lsPS3S
&::after {
padding-right: px2rem(16px);
content: "";
// Adjust for right-to-left languages
[dir="rtl"] & {
padding-right: initial;
padding-left: px2rem(16px);
}
}
}
}
// ----------------------------------------------------------------------------
// Placeholders: improve colocation for better compression
// ----------------------------------------------------------------------------

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Tasklist variables
:root {
--md-tasklist-icon:
svg-load("octicons/check-circle-fill-24.svg");

View File

@@ -24,14 +24,14 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Clipboard button variables
:root {
--md-clipboard-icon: svg-load("material/content-copy.svg");
}
// ----------------------------------------------------------------------------
// Button to copy to clipboard
// Clipboard button
.md-clipboard {
position: absolute;
top: px2em(8px);
@@ -63,8 +63,7 @@
}
// Button on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}
@@ -82,7 +81,7 @@
content: "";
}
// Inline button
// Inline clipboard button
&--inline {
cursor: pointer;
@@ -94,8 +93,7 @@
}
// Code block on focus/hover
&:focus code,
&:hover code {
&:is(:focus, :hover) code {
color: var(--md-accent-fg-color);
background-color: var(--md-accent-fg-color--transparent);
}

View File

@@ -54,8 +54,7 @@
}
// Footer link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
opacity: 0.7;
}
@@ -152,8 +151,7 @@
color: var(--md-footer-fg-color--light);
// Text link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-footer-fg-color);
}
}

View File

@@ -49,8 +49,7 @@
}
// Button on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-bg-color);
background-color: var(--md-accent-fg-color);
border-color: var(--md-accent-fg-color);
@@ -69,8 +68,7 @@
transition: box-shadow 250ms;
// Input on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
box-shadow:
0 px2rem(8px) px2rem(20px) hsla(0, 0%, 0%, 0.15),
0 px2rem(0.5px) px2rem(1px) hsla(0, 0%, 0%, 0.15);

View File

@@ -109,8 +109,7 @@
}
// Image or icon
img,
svg {
:is(img, svg) {
display: block;
width: px2rem(24px);
height: px2rem(24px);

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Navigation variables
:root {
--md-nav-icon--prev: svg-load("material/arrow-left.svg");
--md-nav-icon--next: svg-load("material/chevron-right.svg");
@@ -60,8 +60,7 @@
&.md-logo {
// Image or icon
img,
svg {
:is(img, svg) {
display: block;
width: px2rem(48px);
height: px2rem(48px);
@@ -122,8 +121,7 @@
}
// Navigation link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}
@@ -185,8 +183,7 @@
&--primary {
// Navigation title and item
.md-nav__title,
.md-nav__item {
:is(.md-nav__title, .md-nav__item) {
font-size: px2rem(16px);
line-height: 1.5;
}
@@ -285,8 +282,7 @@
color: var(--md-typeset-a-color);
// Navigation link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}
}
@@ -523,8 +519,7 @@
}
// Show nested navigation when toggle is active or indeterminate
&__toggle:checked ~ &,
&__toggle:indeterminate ~ & {
&__toggle:is(:checked, :indeterminate) ~ & {
display: block;
}

View File

@@ -24,7 +24,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Search variables
:root {
--md-search-result-icon: svg-load("material/file-search-outline.svg");
}
@@ -613,8 +613,7 @@
scroll-snap-align: start;
// Search result link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
background-color: var(--md-accent-fg-color--transparent);
}
@@ -649,8 +648,7 @@
}
// Search result more link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
background-color: var(--md-accent-fg-color--transparent);
}

View File

@@ -29,7 +29,7 @@
position: relative;
z-index: 1;
// Selection bubble
// Selection tooltip
&__inner {
position: absolute;
top: calc(100% - #{px2rem(4px)});
@@ -50,8 +50,7 @@
max-height 0ms 500ms;
// Selection bubble on parent focus/hover
.md-select:focus-within &,
.md-select:hover & {
.md-select:is(:focus-within, :hover) & {
max-height: px2rem(200px);
transform: translate3d(-50%, 0, 0);
opacity: 1;
@@ -113,8 +112,7 @@
}
// Link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}

View File

@@ -56,7 +56,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Repository information variables
:root {
--md-source-forks-icon: svg-load("octicons/repo-forked-16.svg");
--md-source-repositories-icon: svg-load("octicons/repo-16.svg");

View File

@@ -87,8 +87,7 @@
// Active link and link on focus/hover
&--active,
&:focus,
&:hover {
&:is(:focus, :hover) {
color: inherit;
opacity: 1;
}

View File

@@ -40,38 +40,70 @@
}
// ----------------------------------------------------------------------------
// Rules
// Rules: layout
// ----------------------------------------------------------------------------
// Tooltip variables
:root {
--md-tooltip-width: #{px2rem(400px)};
}
// ----------------------------------------------------------------------------
// Tooltip
.md-tooltip {
position: absolute;
// Hack: set an explicit `z-index` so we can transition it to ensure that any
// following elements are not overlaying the tooltip during the transition.
z-index: 0;
top: var(--md-tooltip-y);
left:
clamp(
#{px2rem(16px)},
calc(var(--md-tooltip-x) - #{px2rem(16px)}),
calc(100vw - var(--md-tooltip-width) - #{px2rem(16px)})
);
width: var(--md-tooltip-width);
max-width: calc(100vw - 2 * #{px2rem(16px)});
max-height: 0;
overflow: auto;
color: var(--md-default-fg-color);
background-color: var(--md-default-bg-color);
border-radius: px2rem(2px);
box-shadow:
0 px2rem(4px) px2rem(10px) hsla(0, 0%, 0%, 0.1),
0 0 px2rem(1px) hsla(0, 0%, 0%, 0.25);
transform: translateY(px2rem(8px));
transform: translateY(px2rem(-8px));
// Hack: promote to own layer to reduce jitter
backface-visibility: hidden;
opacity: 0;
transition:
transform 250ms 375ms,
transform 0ms 250ms,
opacity 250ms,
max-height 0ms 250ms,
z-index 250ms;
max-height 0ms 250ms;
// Disable animation for motion reduction preference
// [reduced-motion]: Disable animation
@media (prefers-reduced-motion) {
transition: none;
}
// Tooltip on parent focus
:focus-within > & {
max-height: 1000%;
transform: translateY(0);
opacity: 1;
transition:
transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
opacity 250ms,
max-height 250ms;
// [reduced-motion]: Disable animation
@media (prefers-reduced-motion) {
transition: none;
}
}
// Show outline for keyboard devices
.focus-visible > & {
outline: var(--md-accent-fg-color) auto;
}
// Tooltip wrapper
&__inner {
padding: px2rem(16px);
@@ -87,100 +119,120 @@
margin-bottom: 0;
}
}
// Tooltip on parent focus
:focus > &,
:focus-within > & {
max-height: 1000%;
transform: translateY(0);
opacity: 1;
transition:
transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
opacity 250ms,
max-height 250ms 0ms,
z-index 0ms;
// Disable animation for motion reduction preference
@media (prefers-reduced-motion) {
transition: none;
}
}
// Show outline for keyboard devices
.focus-visible > & {
outline: var(--md-accent-fg-color) auto;
}
}
// ----------------------------------------------------------------------------
// Annotation
// Code annotation
.md-annotation {
white-space: initial;
white-space: normal;
outline: none;
// Code annotation is not hidden (e.g. when copying)
&:not([hidden]) {
display: inline-block;
}
// Promote children to top on focus
&:focus-within > * {
z-index: 2;
}
// Annotation is visible
&:not([hidden]) {
display: inline-block;
}
// Code annotation wrapper (= tooltip)
&__inner {
top: calc(var(--md-tooltip-y) + 1.2ch);
left:
clamp(
#{px2rem(0px)},
calc(var(--md-tooltip-x) + #{px2rem(12px)}),
calc(100vw - var(--md-tooltip-width) - 2 * #{px2rem(16px)})
);
font-family: var(--md-text-font-family);
// Annotation index
&__index {
position: relative;
z-index: 0;
display: inline-block;
min-width: 1.4em;
padding: 0 px2em(6px);
color: hsla(0, 0%, 100%, 1);
text-align: center;
background-color: var(--md-default-fg-color--lighter);
border-radius: px2em(20px);
cursor: pointer;
transition:
color 250ms,
background-color 250ms,
z-index 250ms;
animation: pulse 2000ms infinite;
user-select: none;
// [mobile -]: Align with body copy
@include break-to-device(mobile) {
// Disable animation for motion reduction preference
@media (prefers-reduced-motion) {
transition: none;
animation: none;
}
// Annotation index on focus
:focus-within > & {
transition:
color 250ms,
background-color 250ms,
z-index 0ms;
animation: none;
// Disable animation for motion reduction preference
@media (prefers-reduced-motion) {
transition: none;
// Top-level code block
.md-content__inner > :is(pre, .highlight) & {
left:
clamp(
#{px2rem(16px)},
calc(var(--md-tooltip-x) + #{px2rem(12px)}),
calc(100vw - var(--md-tooltip-width) - #{px2rem(16px)})
);
}
}
// Annotation index on focus/hover
:focus-within > &,
:hover > & {
color: var(--md-accent-bg-color);
background-color: var(--md-accent-fg-color);
// Code annotation tooltip when not focused
:not(:focus-within) > & {
user-select: none;
pointer-events: none;
}
}
// Annotation tooltip
.md-tooltip {
min-width: px2rem(320px);
max-width: 60%;
margin: px2em(-16px, 13.6px) px2em(10px, 13.6px) 0;
font-family: var(--md-text-font-family);
// Code annotation index
&__index {
position: relative;
z-index: 0;
margin: 0 1ch;
color: hsla(0, 0%, 100%, 1);
cursor: pointer;
user-select: none;
// Code annotation index bubble the bubble must be positioned absolutely
// behind the index, because it shouldn't impact the rendering of a code
// block. Otherwise, small rounding differences in browsers can sometimes
// mess up alignment of text following a code annotation.
&::after {
position: absolute;
top: 0.1ch;
left: -0.2ch;
z-index: -1;
width: max(2.2ch, 100% + 1.2ch);
height: 2.2ch;
margin: 0 -0.4ch;
padding: 0 0.4ch;
background-color: var(--md-default-fg-color--lighter);
border-radius: 1em;
transition:
color 250ms,
background-color 250ms;
animation: pulse 2000ms infinite;
content: "";
// [reduced-motion]: Disable animation
@media (prefers-reduced-motion) {
transition: none;
animation: none;
}
// Code annotation index bubble on focus/hover
:is(:focus-within, :hover) > & {
background-color: var(--md-accent-fg-color);
}
// Code annotation index bubble on focus
:focus-within > & {
transition:
color 250ms,
background-color 250ms;
animation: none;
// [reduced-motion]: Disable animation
@media (prefers-reduced-motion) {
transition: none;
}
}
}
// Code annotation index on focus/hover
:is(:focus-within, :hover) > & {
color: var(--md-accent-bg-color);
}
// Code annotation index on focus
:focus-within > & {
animation: none;
}
}
}

View File

@@ -72,8 +72,7 @@
}
// Back-to-top button on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-bg-color);
background-color: var(--md-accent-fg-color);
}

View File

@@ -35,7 +35,7 @@
// Rules
// ----------------------------------------------------------------------------
// Icon definitions
// Version selection variables
:root {
--md-version-icon: svg-load("fontawesome/solid/caret-down.svg");
}
@@ -110,8 +110,7 @@
scroll-snap-type: y mandatory;
// Version selection list on parent focus/hover
.md-version:focus-within &,
.md-version:hover & {
.md-version:is(:focus-within, :hover) & {
max-height: px2rem(200px);
opacity: 1;
transition:
@@ -160,8 +159,7 @@
}
// Link on focus/hover
&:focus,
&:hover {
&:is(:focus, :hover) {
color: var(--md-accent-fg-color);
}