| 1 | /* busy.css — the one part of rastrillo's busy rule that lives in tokens.css: |
| 2 | the spinner takes the label's place. Copied byte for byte from rastrillo's |
| 3 | ui/tokens.css (estilo busy/provenance.json), for apps whose vendored |
| 4 | tokens.css predates it. Link it after tokens.css. */ |
| 5 | |
| 6 | /* With no busy label there are no words to show while it works, so the |
| 7 | spinner takes the label's place instead of joining it: joining widens |
| 8 | the button mid-click and shoves whatever sits beside it. The shim |
| 9 | marks a button that HAS a busy label with data-idle-label, which is |
| 10 | what the :not() keys on — those words were asked for, so they stay. |
| 11 | |
| 12 | :has(> spinner) because aria-busy is ordinary ARIA an app may set on |
| 13 | its own fetch button with no shim and no spinner; keyed on aria-busy |
| 14 | alone, that button would lose its label and show nothing at all. |
| 15 | |
| 16 | The label is unpainted, never removed: aria-busy plus the untouched |
| 17 | text is what a screen reader announces. -webkit-text-fill-color |
| 18 | rather than color, because the ring is drawn in currentColor — a |
| 19 | transparent color would take the spinner with it. Element children |
| 20 | (an icon) are not reached by the text fill and take opacity 0, which |
| 21 | unlike visibility keeps them in the accessibility tree. |
| 22 | |
| 23 | The media query stands the swap down for two readers a lone ring |
| 24 | fails. Under reduced motion the ring is still and dimmed, and on its |
| 25 | own reads as a stray "C". Under forced colours the engine repaints |
| 26 | the text fill, so the label paints anyway and a centred ring would |
| 27 | land on top of it. Both keep the older spinner-beside-label look. */ |
| 28 | @media (prefers-reduced-motion: no-preference) and (forced-colors: none) { |
| 29 | .rst-btn[aria-busy="true"]:not([data-idle-label]):has(> .rst-spin), [rst-btn][aria-busy="true"]:not([data-idle-label]):has(> [rst-spin]) { |
| 30 | -webkit-text-fill-color: transparent; |
| 31 | } |
| 32 | .rst-btn[aria-busy="true"]:not([data-idle-label]):has(> .rst-spin) > :not(.rst-spin), [rst-btn][aria-busy="true"]:not([data-idle-label]):has(> [rst-spin]) > :not([rst-spin]) { |
| 33 | opacity: 0; |
| 34 | } |
| 35 | .rst-btn[aria-busy="true"]:not([data-idle-label]) > .rst-spin, [rst-btn][aria-busy="true"]:not([data-idle-label]) > [rst-spin] { |
| 36 | inset: 0; |
| 37 | margin: auto; |
| 38 | position: absolute; |
| 39 | } |
| 40 | /* The spinner's containing block. Zero specificity, so an app's own |
| 41 | position: fixed or sticky on a floating Save still wins — either |
| 42 | of those contains the spinner just as well, and losing it would |
| 43 | drop the button back into the page flow mid-click. */ |
| 44 | :where(.rst-btn[aria-busy="true"]:not([data-idle-label])), :where([rst-btn][aria-busy="true"]:not([data-idle-label])) { |
| 45 | position: relative; |
| 46 | } |
| 47 | } |
| 48 | |