@sandlada/mdc
Version:
@sandlada/mdc is an open source component library that follows the Material Design 3 design specifications.
100 lines • 3.8 kB
TypeScript
/**
* @license
* Copyright 2026 Kai-Orion & Sandlada
* SPDX-License-Identifier: MIT
*/
import { type ReactiveController, type ReactiveControllerHost } from 'lit';
/**
* Configuration options for {@link MeasuredDimensionController}.
*/
export interface MeasuredDimensionOptions {
/**
* Returns the target element whose natural size is observed and animated.
* May return `null` before the element has been rendered.
*/
target: () => HTMLElement | null;
/**
* The CSS dimension to animate.
* @default 'width'
*/
dimension?: 'width' | 'height';
/**
* Animation duration in milliseconds.
* @default 300
*/
duration?: number;
/**
* CSS easing function for the animation.
* @default 'cubic-bezier(0.2, 0, 0.2, 1)'
*/
easing?: string;
}
/**
* A `ReactiveController` that animates a single CSS dimension (width or
* height) of a target element whenever its natural measured size changes.
*
* ### Trigger sources
* - **Light DOM mutations** (e.g. slotted text content changes, slot child
* additions / removals): detected via a `MutationObserver` on the host.
* Measurement is deferred to the next animation frame so that any concurrent
* Lit re-renders (e.g. `slotchange` → `hasIcon`) complete before we read
* the natural size.
* - **Lit re-renders** (e.g. `size`, `hasIcon`, `hasLabel` class changes):
* detected via `hostUpdated()`. Skipped when a mutation rAF is already
* pending to avoid animating twice for the same visual change.
*
* ### Animation
* Uses the Web Animations API (`element.animate()`) instead of inline CSS
* transitions, which allows mid-flight interruption without jank: when a new
* change arrives while an animation is running, the controller reads the
* current compositor position via `getBoundingClientRect()`, cancels the
* old animation (so the natural size becomes reliable again), then starts a
* new animation from that visual position to the new natural size.
*
* @example
* ```ts
* private readonly sizeController = new MeasuredDimensionController(this, {
* target: () => this.labelElement,
* dimension: 'width',
* duration: 250,
* })
* ```
*/
export declare class MeasuredDimensionController implements ReactiveController {
private readonly host;
private readonly options;
/** Last known settled natural size of the target element. */
private naturalSize;
/** Currently running WAAPI animation, if any. */
private currentAnimation;
private mutationObserver;
/** Non-null when a measurement rAF is already queued (coalescing guard). */
private rafId;
constructor(host: ReactiveControllerHost & Element, options: MeasuredDimensionOptions);
hostConnected(): void;
hostDisconnected(): void;
/**
* Handles Lit re-render triggered size changes (e.g. `size` property,
* shadow DOM structural changes like `has-icon` / `has-label` classes).
* Defers to any already-queued mutation rAF to avoid double-animating.
*/
hostUpdated(): void;
/**
* MutationObserver callback for light DOM changes (slotted text / children).
* Defers to the next animation frame so that Lit re-renders triggered by
* the same mutation (e.g. slotchange → `hasIcon = true`) complete before
* we read the natural size.
*/
private readonly onMutation;
/**
* Captures the visual from-size, cancels any in-progress animation so
* that the natural size is reliable, then starts a new animation if the
* natural size has changed.
*/
private measure;
private animate;
private readNaturalSize;
private cancelAnimation;
private cancelPendingRaf;
}
//# sourceMappingURL=measured-dimension-controller.d.ts.map