@sandlada/mdc
Version:
@sandlada/mdc is an open source component library that follows the Material Design 3 design specifications.
401 lines (366 loc) • 15.9 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{ExpressiveProgressIndicatorDefinition as i}from"../../component-definitions/expressive-progress-indicator.definition.js";
/**
* @license
* Copyright 2026 Kai-Orion & Sandlada
* SPDX-License-Identifier: MIT
*/
const a=r(i,{expandShapes:!1,useBaseFallback:!0,prefix:`--mdc-expressive-progress-indicator`}),o=t(n(a,!0).join(``)),s=t(`650ms`),c=t(`cubic-bezier(0.16, 1, 0.3, 1)`),l=t(`1750ms`),u=t(`6000ms`),d=t(`cubic-bezier(0.4, 0, 0.2, 1)`),f=e`
@layer mdc.expressive-progress-indicator.variable {
:host { ${o}; }
}
@layer mdc.expressive-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;
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,
.track,
.active-bar,
.stop-indicator,
.bar,
.bar-inner,
.circular-spinner,
svg,
.track-circle,
.active-circle,
.spinner-arc {
position: absolute;
inset: 0;
}
:host([variant='linear']) .progress {
/* Animations stay LTR; RTL is handled by flipping the host. */
direction: ltr;
border-radius: inherit;
overflow: hidden;
}
:host([variant='circular']) .progress {
flex: 1;
align-self: stretch;
margin: 4px;
}
/* ── Linear: determinate ───────────────────────────────────────────── */
.active-bar {
height: var(--_linear-active-indicator-thickness);
width: calc(var(--_fraction) * 100%);
border-start-start-radius: var(--_active-indicator-shape-start-start);
border-start-end-radius: var(--_active-indicator-shape-start-end);
border-end-start-radius: var(--_active-indicator-shape-end-start);
border-end-end-radius: var(--_active-indicator-shape-end-end);
background: var(--_enabled-active-indicator-color);
transition: width ${s} ${c};
}
/*
* The track starts after the active indicator plus a visual gap.
* Round caps are drawn inside the element box (border-radius), so the
* raw gap size is used directly - no cap compensation needed.
*/
.track {
height: var(--_linear-track-thickness);
inset-inline-start: calc(
var(--_fraction) * 100% +
min(var(--_fraction) * 100%, var(--_linear-track-gap))
);
inset-inline-end: 0;
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);
background: var(--_enabled-track-color);
transition: inset-inline-start ${s} ${c};
}
/* Round stop indicator at the trailing edge of the track. */
.stop-indicator {
width: var(--_linear-stop-indicator-size);
height: var(--_linear-stop-indicator-size);
inset-inline-end: 0;
top: 50%;
transform: translateY(-50%);
border-start-start-radius: var(--_stop-indicator-shape-start-start);
border-start-end-radius: var(--_stop-indicator-shape-start-end);
border-end-start-radius: var(--_stop-indicator-shape-end-start);
border-end-end-radius: var(--_stop-indicator-shape-end-end);
background: var(--_enabled-stop-indicator-color);
}
/* ── Linear: indeterminate ──────────────────────────────────────────── */
.bar {
width: 100%;
height: var(--_linear-active-indicator-thickness);
transform-origin: left center;
}
.secondary-bar {
display: none;
}
.bar-inner {
inset: 0;
background: var(--_enabled-active-indicator-color);
}
:host([variant='linear'][indeterminate]) .primary-bar {
inset-inline-start: -145.167%;
animation: linear infinite expressive-primary-translate ${l};
}
:host([variant='linear'][indeterminate]) .primary-bar > .bar-inner {
animation: linear infinite expressive-primary-scale ${l};
}
:host([variant='linear'][indeterminate]) .secondary-bar {
inset-inline-start: -54.8889%;
display: block;
animation: linear infinite expressive-secondary-translate ${l};
}
:host([variant='linear'][indeterminate]) .secondary-bar > .bar-inner {
animation: linear infinite expressive-secondary-scale ${l};
}
/* The track stays full-width while indeterminate (Flutter behavior),
regardless of the current value. */
:host([variant='linear'][indeterminate]) .track {
inset-inline-start: 0;
transition: none;
}
/* Only one linear mode renders at a time */
:host([variant='linear'][indeterminate]) .active-bar,
:host([variant='linear'][indeterminate]) .stop-indicator {
display: none;
}
:host([variant='linear']:not([indeterminate])) .bar {
display: none;
}
/* RTL: flip the whole indicator instead of mirroring each keyframe */
:host([variant='linear']:dir(rtl)) {
transform: scale(-1);
}
/* ── Circular: shared SVG ──────────────────────────────────────────── */
svg {
transform: rotate(-90deg);
}
circle {
cx: 50%;
cy: 50%;
r: calc(50% * (1 - var(--_active-indicator-width) / 100));
stroke-width: calc(var(--_active-indicator-width) * 1%);
stroke-dasharray: 100;
/* MD3E uses round stroke caps everywhere */
stroke-linecap: round;
fill: transparent;
}
/*
* --_active-indicator-width is a unitless percentage equal to
* stroke-width / (size - 2 * padding) * 100, consumed by the circle
* r / stroke-width calc()s so the stroke stays 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: determinate ─────────────────────────────────────────── */
.active-circle {
stroke: var(--_enabled-active-indicator-color);
transition: stroke-dashoffset ${s} ${c};
}
/*
* Track arc: drawn only while determinate, inset from the active arc
* by the track gap. dasharray/dashoffset are computed in the
* component from the Compose gap formula.
*/
.track-circle {
stroke: var(--_enabled-track-color);
transition:
stroke-dashoffset ${s} ${c},
stroke-dasharray ${s} ${c};
}
/* ── Circular: indeterminate spinner ───────────────────────────────── */
.circular-spinner {
animation: expressive-circular-rotate ${u}
linear infinite;
}
.spinner-arc {
stroke: var(--_enabled-active-indicator-color);
animation: expressive-circular-sweep ${u}
${d} infinite;
}
:host([variant='circular']:not([indeterminate])) .circular-spinner {
display: none;
}
:host([variant='circular'][indeterminate]) .track-circle,
:host([variant='circular'][indeterminate]) .active-circle {
display: none;
}
/* ── Keyframes: linear indeterminate (from @material/web, 1750ms) ─── */
@keyframes expressive-primary-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 expressive-secondary-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 expressive-primary-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 expressive-secondary-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%);
}
}
/* ── Keyframes: circular indeterminate (Compose) ───────────────────── */
/* Rotate 1440deg over the full cycle (1080 global + 360 additional). */
@keyframes expressive-circular-rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(1440deg);
}
}
/*
* Sweep the arc between 0.1 (dashoffset 90, 36deg) and 0.87
* (dashoffset 13, 313deg) of the circle using the standard easing.
*/
@keyframes expressive-circular-sweep {
0% {
stroke-dashoffset: 90;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
50% {
stroke-dashoffset: 13;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
100% {
stroke-dashoffset: 90;
}
}
/* ── Reduced motion ────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.progress,
.bar,
.bar-inner,
.circular-spinner,
.spinner-arc {
animation: none !important;
transition: none !important;
}
/* Static fallbacks so an indeterminate indicator stays visible */
:host([variant='linear'][indeterminate]) .primary-bar {
inset-inline-start: 0;
width: 40%;
}
:host([variant='linear'][indeterminate]) .secondary-bar {
display: none;
}
:host([variant='circular'][indeterminate]) .spinner-arc {
stroke-dashoffset: 75;
}
}
/* ── Forced colors ─────────────────────────────────────────────────── */
@media (forced-colors: active) {
:host {
outline: 1px solid CanvasText;
}
.active-bar,
.bar-inner {
background-color: CanvasText;
}
.active-circle,
.spinner-arc {
stroke: CanvasText;
}
.stop-indicator {
background-color: CanvasText;
}
}
}
`;export{f as ExpressiveProgressIndicatorStyles};
//# sourceMappingURL=expressive-progress-indicator.style.js.map