@sandlada/mdc
Version:
@sandlada/mdc is an open source component library that follows the Material Design 3 design specifications.
388 lines (353 loc) • 14.2 kB
JavaScript
import{css as e,unsafeCSS as t}from"../../node_modules/@lit/reactive-element/css-tag.js";import"../../node_modules/lit/index.js";import{defineVars as n}from"../../node_modules/@sandlada/jss/dist/define-vars.js";import{defineTokenRefsRecord as r}from"../../node_modules/@sandlada/jss/dist/define-token-refs.js";import{ProgressIndicatorDefinition as i}from"../../component-definitions/progress-indicator.definition.js";
/**
* @license
* Copyright 2026 Kai-Orion & Sandlada
* SPDX-License-Identifier: MIT
*/
const a=r(i,{expandShapes:!1,useBaseFallback:!0,prefix:`--mdc-progress-indicator`}),o=t(n(a,!0).join(``)),s=t(`250ms`),c=t(`cubic-bezier(0.4, 0, 0.6, 1)`),l=t(`2s`),u=t(`500ms`),d=t(`cubic-bezier(0, 0, 0.2, 1)`),f=t(`1333ms`),p=t(`5332ms`),m=t(`1568ms`),h=t(`cubic-bezier(0.4, 0, 0.2, 1)`),g=e`
@layer mdc.progress-indicator.variable {
:host { ${o}; }
}
@layer mdc.progress-indicator.base {
/* ── Host ──────────────────────────────────────────────────────────── */
:host([variant='linear']) {
border-start-start-radius: var(--_track-shape-start-start);
border-start-end-radius: var(--_track-shape-start-end);
border-end-start-radius: var(--_track-shape-end-start);
border-end-end-radius: var(--_track-shape-end-end);
display: flex;
position: relative;
/* matches the meter element — just gives a default width */
min-width: 80px;
height: var(--_linear-track-thickness);
/* Skip off-screen rendering. content-visibility pauses animations
only while the element is skipped and resumes them on scroll,
which is fine for these tiny indicators. */
content-visibility: auto;
contain: strict;
}
:host([variant='circular']) {
display: inline-flex;
vertical-align: middle;
position: relative;
align-items: center;
justify-content: center;
}
:host([variant='circular'][circular-size='extra-small']) {
width: var(--_circular-extra-small-size);
height: var(--_circular-extra-small-size);
}
:host([variant='circular'][circular-size='small']) {
width: var(--_circular-small-size);
height: var(--_circular-small-size);
}
:host([variant='circular'][circular-size='medium']) {
width: var(--_circular-medium-size);
height: var(--_circular-medium-size);
}
:host([variant='circular'][circular-size='large']) {
width: var(--_circular-large-size);
height: var(--_circular-large-size);
}
/* ── Progress container ────────────────────────────────────────────── */
.progress,
.inactive-track,
.bar,
.bar-inner,
.spinner,
.left,
.right,
.circle,
svg,
.track,
.active-track {
position: absolute;
inset: 0;
}
:host([variant='linear']) .progress {
/* Animations need to be in LTR. We support RTL by flipping the
whole indicator with scale(-1). */
direction: ltr;
border-radius: inherit;
overflow: hidden;
display: flex;
align-items: center;
}
:host([variant='circular']) .progress {
flex: 1;
align-self: stretch;
margin: 4px;
}
/* ── Linear: bars ──────────────────────────────────────────────────── */
.bar {
animation: none;
/* position is offset for indeterminate animation, so lock the
inline size here */
width: 100%;
height: var(--_linear-active-indicator-thickness);
transform-origin: left center;
transition: transform ${s} ${c};
}
.secondary-bar {
display: none;
}
.bar-inner {
inset: 0;
animation: none;
background: var(--_enabled-active-indicator-color);
}
.inactive-track {
background: var(--_enabled-track-color);
transition: transform ${s} ${c};
transform-origin: left center;
}
:host([variant='linear'][indeterminate]) .bar {
transition: none;
}
:host([variant='linear'][indeterminate]) .primary-bar {
inset-inline-start: -145.167%;
animation: linear infinite primary-indeterminate-translate ${l};
}
:host([variant='linear'][indeterminate]) .primary-bar > .bar-inner {
animation: linear infinite primary-indeterminate-scale ${l};
}
:host([variant='linear'][indeterminate]) .secondary-bar {
inset-inline-start: -54.8889%;
display: block;
animation: linear infinite secondary-indeterminate-translate ${l};
}
:host([variant='linear'][indeterminate]) .secondary-bar > .bar-inner {
animation: linear infinite secondary-indeterminate-scale ${l};
}
/* RTL: flip the whole indicator instead of mirroring each keyframe */
:host([variant='linear']:dir(rtl)) {
transform: scale(-1);
}
/* ── Circular: determinate SVG ─────────────────────────────────────── */
svg {
transform: rotate(-90deg);
}
circle {
cx: 50%;
cy: 50%;
/* scale the radius so the stroke (whose outer edge lands on the
50% box edge via --_active-indicator-width) stays fully inside */
r: calc(50% * (1 - var(--_active-indicator-width) / 100));
stroke-width: calc(var(--_active-indicator-width) * 1%);
/* pathLength=100 normalizes dash values */
stroke-dasharray: 100;
fill: transparent;
}
.track {
/* MD3 circular has no track */
stroke: transparent;
}
.active-track {
transition: stroke-dashoffset ${u} ${d};
stroke: var(--_enabled-active-indicator-color);
}
/*
* --_active-indicator-width is a unitless percentage equal to
* stroke-width / (size - 2 * padding) * 100. It is consumed by the
* circle r / stroke-width calc()s so that every size renders the
* stroke flush inside the padding box.
*/
:host([variant='circular'][circular-size='extra-small']) {
--_active-indicator-width: 16.6667;
}
:host([variant='circular'][circular-size='small']) {
--_active-indicator-width: 10;
}
:host([variant='circular'][circular-size='medium']) {
--_active-indicator-width: 7.1429;
}
:host([variant='circular'][circular-size='large']) {
--_active-indicator-width: 7.5;
}
/* ── Circular: indeterminate spinner ───────────────────────────────── */
/* The rotate must be scoped to the circular variant: the linear
indicator's .progress also carries the 'indeterminate' class, and
an ungated rule would spin the whole linear bar (576x4 rotated
~90deg renders as a 4x576 sliver). */
:host([variant='circular']) .progress.indeterminate {
animation: linear infinite linear-rotate ${m};
}
.spinner {
animation: infinite both rotate-arc ${p} ${h};
}
.left {
overflow: hidden;
inset: 0 50% 0 0;
}
.right {
overflow: hidden;
inset: 0 0 0 50%;
}
.circle {
box-sizing: border-box;
border-radius: 50%;
border-style: solid;
border-color: var(--_enabled-active-indicator-color)
var(--_enabled-active-indicator-color) transparent transparent;
animation: expand-arc ${f} ${h}
infinite both;
}
:host([variant='circular'][circular-size='extra-small']) .circle {
border-width: var(--_circular-extra-small-stroke-width);
}
:host([variant='circular'][circular-size='small']) .circle {
border-width: var(--_circular-small-stroke-width);
}
:host([variant='circular'][circular-size='medium']) .circle {
border-width: var(--_circular-medium-stroke-width);
}
:host([variant='circular'][circular-size='large']) .circle {
border-width: var(--_circular-large-stroke-width);
}
.left .circle {
rotate: 135deg;
inset: 0 -100% 0 0;
}
.right .circle {
rotate: 100deg;
inset: 0 0 0 -100%;
animation-delay: calc(-0.5 * ${f});
}
/* Show only the active rendering mode */
:host([variant='circular']:not([indeterminate])) .spinner {
display: none;
}
:host([variant='circular'][indeterminate]) svg {
display: none;
}
/* ── Linear indeterminate keyframes (from @material/web) ──────────── */
@keyframes primary-indeterminate-scale {
0% {
transform: scaleX(0.08);
}
36.65% {
animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
transform: scaleX(0.08);
}
69.15% {
animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
transform: scaleX(0.661479);
}
100% {
transform: scaleX(0.08);
}
}
@keyframes secondary-indeterminate-scale {
0% {
animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
transform: scaleX(0.08);
}
19.15% {
animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.00432);
transform: scaleX(0.457104);
}
44.15% {
animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
transform: scaleX(0.72796);
}
100% {
transform: scaleX(0.08);
}
}
@keyframes primary-indeterminate-translate {
0% {
transform: translateX(0px);
}
20% {
animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
transform: translateX(0px);
}
59.15% {
animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
transform: translateX(83.6714%);
}
100% {
transform: translateX(200.611%);
}
}
@keyframes secondary-indeterminate-translate {
0% {
animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
transform: translateX(0px);
}
25% {
animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
transform: translateX(37.6519%);
}
48.35% {
animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
transform: translateX(84.3862%);
}
100% {
transform: translateX(160.278%);
}
}
/* ── Circular indeterminate keyframes (from @material/web) ────────── */
/* The arc expands/contracts between 265deg and 130deg. */
@keyframes expand-arc {
0% {
transform: rotate(265deg);
}
50% {
transform: rotate(130deg);
}
100% {
transform: rotate(265deg);
}
}
/* The arc travels around the circle: 3/4 arc × 4 rotations = 1080deg. */
@keyframes rotate-arc {
12.5% {
transform: rotate(135deg);
}
25% {
transform: rotate(270deg);
}
37.5% {
transform: rotate(405deg);
}
50% {
transform: rotate(540deg);
}
62.5% {
transform: rotate(675deg);
}
75% {
transform: rotate(810deg);
}
87.5% {
transform: rotate(945deg);
}
100% {
transform: rotate(1080deg);
}
}
/* The traveling arc is spun linearly for the spinner effect. */
@keyframes linear-rotate {
to {
transform: rotate(360deg);
}
}
/* ── Forced colors ─────────────────────────────────────────────────── */
@media (forced-colors: active) {
:host {
outline: 1px solid CanvasText;
}
.bar-inner {
background-color: CanvasText;
}
.active-track {
stroke: CanvasText;
}
.circle {
border-color: CanvasText CanvasText Canvas Canvas;
}
}
}
`;export{g as ProgressIndicatorStyles};
//# sourceMappingURL=progress-indicator.style.js.map