@motion.page/sdk 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,6 +63,15 @@ interface TextFlapperFunctions {
63
63
  flap: (charElements: HTMLElement[], config: FlapConfig, staggerDelays?: number[], continuous?: boolean) => FlapController;
64
64
  revert: (charElements: HTMLElement[]) => void;
65
65
  }
66
+ /**
67
+ * Factory slot for the optional reactive responsive runtime. The
68
+ * ResponsiveManager module self-registers `create` here when loaded, so
69
+ * core/Motion can instantiate a manager without a static import — keeping the
70
+ * module tree-shaken out of bundles that have no responsive timelines.
71
+ */
72
+ interface ResponsiveFactory {
73
+ create: () => import('../responsive/ResponsiveManager').ResponsiveManager;
74
+ }
66
75
  /**
67
76
  * Unified registry for all optional SDK features.
68
77
  * Each slot is null until the corresponding module loads and self-registers.
@@ -82,5 +91,6 @@ export declare const SDKRegistry: {
82
91
  } | null;
83
92
  styleReset: StyleResetFunctions | null;
84
93
  fit: FitResolverFunctions | null;
94
+ responsive: ResponsiveFactory | null;
85
95
  };
86
96
  export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * ResponsiveManager (Responsive Editing — Stage 2, reactive SDK runtime)
3
+ *
4
+ * Registers a set of per-breakpoint timeline variants and live-swaps between
5
+ * them as the viewport crosses a breakpoint boundary. This upgrades the Stage 1
6
+ * compile-time output (N mutually-exclusive `if(matchMedia(...).matches)` blocks
7
+ * evaluated once at load) to a single `Motion.responsive(...)` call whose active
8
+ * variant follows the viewport in real time.
9
+ *
10
+ * The breakpoint queries are byte-for-byte the SAME strict, non-overlapping
11
+ * mutual-exclusion ranges used by Stage 1 `buildResponsiveBlocks` (RD-2):
12
+ *
13
+ * desktop screen and (min-width: L+1)
14
+ * laptop screen and (min-width: T+1) and (max-width: L)
15
+ * tablet screen and (min-width: P+1) and (max-width: T)
16
+ * phone screen and (max-width: P)
17
+ *
18
+ * Exactly one query matches any given width, so the active tier is unambiguous.
19
+ *
20
+ * On activation the manager KILLS the previously-active variant's timeline
21
+ * (cleaning up its triggers + restoring element state) and then invokes the new
22
+ * tier's factory, which rebuilds and (re)registers the timeline. A variant
23
+ * factory returns the `Timeline` it builds so the manager can kill it on the
24
+ * next swap.
25
+ *
26
+ * Tree-shaking: this module self-registers a factory on `SDKRegistry.responsive`
27
+ * and is referenced by `Motion.responsive` only through that registry — never via
28
+ * a static import. It is therefore only bundled when a project actually has a
29
+ * responsive timeline (see `feature:responsive` in the sdk-generator bundler).
30
+ */
31
+ /** The four tiers in cascade order (widest → narrowest). */
32
+ export type ResponsiveTier = 'desktop' | 'laptop' | 'tablet' | 'phone';
33
+ /**
34
+ * A variant factory: builds + registers the tier's timeline and returns it.
35
+ * Returning the timeline lets the manager kill it precisely on the next swap.
36
+ * A factory MAY return nothing (e.g. a tier whose nodes are all disabled);
37
+ * in that case the manager simply has no active timeline for that tier.
38
+ */
39
+ export type ResponsiveVariant = () => {
40
+ kill: (clearProps?: boolean) => void;
41
+ } | void;
42
+ /** Map of tier → variant factory. Tiers may be sparse; missing tiers cascade up. */
43
+ export type ResponsiveVariants = Partial<Record<ResponsiveTier, ResponsiveVariant>>;
44
+ /** Global breakpoint pixel boundaries (same values the builder stores). */
45
+ export interface ResponsiveBreakpointConfig {
46
+ laptops: number;
47
+ tablets: number;
48
+ phones: number;
49
+ }
50
+ export declare class ResponsiveManager {
51
+ private variants;
52
+ private queries;
53
+ private activeTier;
54
+ private activeTimeline;
55
+ private destroyed;
56
+ /**
57
+ * Register the variants and wire up matchMedia listeners. Immediately
58
+ * activates whichever tier currently matches the viewport.
59
+ *
60
+ * Re-entrant: calling register() again (or after destroy()) first tears down
61
+ * any previously-wired listeners and active timeline via destroy(), then
62
+ * re-arms from a clean slate. This prevents matchMedia 'change' listeners from
63
+ * accumulating across re-registrations (which would otherwise leak listeners
64
+ * and trigger duplicate rebuilds on every breakpoint crossing).
65
+ *
66
+ * In non-DOM / no-matchMedia environments it falls back to building the
67
+ * desktop variant a single time (no live swapping).
68
+ */
69
+ register(variants: ResponsiveVariants, config: ResponsiveBreakpointConfig): void;
70
+ /** Re-evaluate which tier matches and swap if it changed. */
71
+ private handleChange;
72
+ /**
73
+ * Kill the current timeline and build the variant for `tier`. When the tier
74
+ * has no own variant, cascade UP to the nearest wider tier that does (so a
75
+ * sparse variant map still covers every width).
76
+ */
77
+ private activateTier;
78
+ /** Kill the active timeline if any, swallowing teardown errors. */
79
+ private killActive;
80
+ /** The tier currently driving the preview (for tests / debugging). */
81
+ getActiveTier(): ResponsiveTier | null;
82
+ /**
83
+ * Tear down all listeners and kill the active timeline. Idempotent.
84
+ */
85
+ destroy(): void;
86
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * PropertyParser constants — property-name sets used to classify and default
3
+ * animation properties.
4
+ */
5
+ export declare const TRANSFORM_PROPS: Set<string>;
6
+ export declare const PX_PROPS: Set<string>;
7
+ export declare const DEG_PROPS: Set<string>;
8
+ export declare const UNITLESS_PROPS: Set<string>;
9
+ export declare const COLOR_PROPS: Set<string>;
10
+ export declare const NAMED_COLORS: Set<string>;
11
+ /**
12
+ * Properties that support "auto" value resolution.
13
+ * Only layout properties where the browser can compute a natural value make sense.
14
+ */
15
+ export declare const AUTO_PROPS: Set<string>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PropertyParser barrel — preserves the public import path `../utils/PropertyParser`.
3
+ *
4
+ * Re-exports the exact public surface the original single-file module had:
5
+ * - types `PropertyValue`, `ParsedProperty`
6
+ * - predicates `isTransformProp`, `isColorProp`, `isFilterProp`, `isDrawSVGProp`,
7
+ * `isPathProp`, `isClipPathProp`, `isCSSVariable`, `looksLikeColor`
8
+ * - unit helpers `parseValue`, `getDefaultUnit`, `getCurrentValue`,
9
+ * `convertPxToUnit`, `getCurrentValueInUnit`, `getOriginalCSSValueWithUnit`,
10
+ * `camelToKebab`
11
+ * - relative-value helpers `isRelativeValue`, `getRelativeOperator`,
12
+ * `calculateRelativeValue`
13
+ * - the main `parseProperty` entry point
14
+ *
15
+ * Unlike TextSplitter/TextFlapper, this module has NO module-load side effect:
16
+ * it only READS from SDKRegistry, it never registers itself. So there is no
17
+ * `./register` import here.
18
+ */
19
+ export type { PropertyValue, ParsedProperty } from './types';
20
+ export { isTransformProp, isColorProp, isFilterProp, isDrawSVGProp, isPathProp, isClipPathProp, isCSSVariable, looksLikeColor, } from './predicates';
21
+ export { parseValue, getDefaultUnit, getCurrentValue, convertPxToUnit, getCurrentValueInUnit, getOriginalCSSValueWithUnit, camelToKebab, } from './units';
22
+ export { isRelativeValue, getRelativeOperator, calculateRelativeValue, } from './relative';
23
+ export { parseProperty } from './parsers';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * PropertyParser parsers — the main `parseProperty` entry point and the
3
+ * per-type parsers it delegates to (color, filter, CSS variable, drawSVG,
4
+ * clip-path, path). The optional parsers read their implementations from
5
+ * SDKRegistry and gracefully no-op when a parser is not loaded.
6
+ */
7
+ import type { AnimationTarget } from '../../types';
8
+ import type { PropertyValue, ParsedProperty } from './types';
9
+ /**
10
+ * Parse a property for animation
11
+ * Handles both DOM Elements and plain objects
12
+ */
13
+ export declare function parseProperty(target: AnimationTarget, prop: string, targetValue: PropertyValue, fromValue?: PropertyValue): ParsedProperty;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * PropertyParser predicates — classify a property by name and detect
3
+ * color-like values.
4
+ */
5
+ import type { PropertyValue } from './types';
6
+ /**
7
+ * Check if a property is a transform property
8
+ */
9
+ export declare function isTransformProp(prop: string): boolean;
10
+ /**
11
+ * Check if a property is a color property
12
+ */
13
+ export declare function isColorProp(prop: string): boolean;
14
+ /**
15
+ * Check if a property is the filter property
16
+ */
17
+ export declare function isFilterProp(prop: string): boolean;
18
+ /**
19
+ * Check if a property is the drawSVG property
20
+ */
21
+ export declare function isDrawSVGProp(prop: string): boolean;
22
+ /**
23
+ * Check if a property is the path property (motion path)
24
+ */
25
+ export declare function isPathProp(prop: string): boolean;
26
+ /**
27
+ * Check if a property is the clip-path property (accepts both kebab and camel case)
28
+ */
29
+ export declare function isClipPathProp(prop: string): boolean;
30
+ /**
31
+ * Check if a property is a CSS variable (custom property)
32
+ */
33
+ export declare function isCSSVariable(prop: string): boolean;
34
+ /**
35
+ * Check if a value looks like a color (for CSS variable type detection)
36
+ */
37
+ export declare function looksLikeColor(value: PropertyValue): boolean;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * PropertyParser relative values — detect and compute relative operators
3
+ * (`+=`, `-=`, `*=`, `/=`).
4
+ */
5
+ import type { PropertyValue } from './types';
6
+ /**
7
+ * Check if a value is relative (+=, -=, *=, /=)
8
+ */
9
+ export declare function isRelativeValue(value: PropertyValue): boolean;
10
+ /**
11
+ * Get relative operator from value string
12
+ */
13
+ export declare function getRelativeOperator(value: string): '+=' | '-=' | '*=' | '/=' | null;
14
+ /**
15
+ * Calculate relative end value
16
+ */
17
+ export declare function calculateRelativeValue(startValue: number, relativeValue: number, operator: '+=' | '-=' | '*=' | '/='): number;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * PropertyParser types — the property-value union and the discriminated union
3
+ * of all parsed property shapes.
4
+ */
5
+ import type { ParsedFilterFunction } from '../FilterParser';
6
+ import type { ParsedDrawSVG } from '../DrawSVGParser';
7
+ import type { ParsedClipPath } from '../ClipPathParser';
8
+ /**
9
+ * Valid property value types for animation
10
+ */
11
+ export type PropertyValue = number | string;
12
+ /**
13
+ * Base fields shared by all parsed property types
14
+ */
15
+ export interface ParsedPropertyBase {
16
+ property: string;
17
+ isTransform: boolean;
18
+ }
19
+ /**
20
+ * Scalar property (numbers with units)
21
+ */
22
+ export interface ParsedScalarProperty extends ParsedPropertyBase {
23
+ type: 'scalar';
24
+ startValue: number;
25
+ endValue: number;
26
+ unit: string;
27
+ }
28
+ /**
29
+ * Color property (RGBA values)
30
+ */
31
+ export interface ParsedColorProperty extends ParsedPropertyBase {
32
+ type: 'color';
33
+ startColor: Float32Array;
34
+ endColor: Float32Array;
35
+ }
36
+ /**
37
+ * Filter property (CSS filters)
38
+ */
39
+ export interface ParsedFilterProperty extends ParsedPropertyBase {
40
+ type: 'filter';
41
+ startFilters: ParsedFilterFunction[];
42
+ endFilters: ParsedFilterFunction[];
43
+ }
44
+ /**
45
+ * DrawSVG property (SVG stroke animation)
46
+ */
47
+ export interface ParsedDrawSVGProperty extends ParsedPropertyBase {
48
+ type: 'drawSVG';
49
+ startDraw: ParsedDrawSVG;
50
+ endDraw: ParsedDrawSVG;
51
+ length: number;
52
+ }
53
+ /**
54
+ * Clip-path property (CSS clip-path shape function interpolation)
55
+ */
56
+ export interface ParsedClipPathProperty extends ParsedPropertyBase {
57
+ type: 'clipPath';
58
+ startClipPath: ParsedClipPath;
59
+ endClipPath: ParsedClipPath;
60
+ }
61
+ /**
62
+ * Path property (motion along SVG path)
63
+ */
64
+ export interface ParsedPathProperty extends ParsedPropertyBase {
65
+ type: 'path';
66
+ pathData: string;
67
+ pathLength: number;
68
+ startProgress: number;
69
+ endProgress: number;
70
+ rotate: boolean;
71
+ alignOffset: {
72
+ x: number;
73
+ y: number;
74
+ };
75
+ pathOffset: {
76
+ x: number;
77
+ y: number;
78
+ };
79
+ }
80
+ /**
81
+ * Discriminated union of all parsed property types
82
+ */
83
+ export type ParsedProperty = ParsedScalarProperty | ParsedColorProperty | ParsedFilterProperty | ParsedDrawSVGProperty | ParsedPathProperty | ParsedClipPathProperty;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * PropertyParser units — numeric/unit parsing, defaulting, and reading current
3
+ * values off a target (with px↔unit conversion via the browser layout engine).
4
+ */
5
+ import type { AnimationTarget } from '../../types';
6
+ import type { PropertyValue } from './types';
7
+ /**
8
+ * Parse a property value and extract numeric value and unit
9
+ */
10
+ export declare function parseValue(value: PropertyValue): {
11
+ value: number;
12
+ unit: string;
13
+ };
14
+ /**
15
+ * Get the default unit for a property
16
+ */
17
+ export declare function getDefaultUnit(prop: string): string;
18
+ /**
19
+ * Get the current computed value of a property from a target
20
+ * Handles both DOM Elements and plain objects
21
+ */
22
+ export declare function getCurrentValue(target: AnimationTarget, prop: string): number;
23
+ /**
24
+ * Convert a pixel value to a target CSS unit by measuring the element in the DOM.
25
+ *
26
+ * Uses the browser's layout engine: temporarily sets `100{targetUnit}` on the
27
+ * element, reads the computed pixel equivalent, then derives the conversion ratio.
28
+ * This handles %, em, rem, vw, vh, vmin, vmax, ch, ex — any unit the browser supports.
29
+ *
30
+ * When the target unit is 'px' or empty, returns the value unchanged (no conversion).
31
+ * Falls back to the raw pixel value if measurement fails (e.g. detached element).
32
+ */
33
+ export declare function convertPxToUnit(element: Element, prop: string, pxValue: number, targetUnit: string): number;
34
+ /**
35
+ * Get the current computed value of a property, converted to the specified unit.
36
+ *
37
+ * Combines getCurrentValue (which always returns px for layout properties)
38
+ * with convertPxToUnit to return the value in the desired CSS unit.
39
+ * Only performs conversion for DOM Element targets with non-px units.
40
+ */
41
+ export declare function getCurrentValueInUnit(target: AnimationTarget, prop: string, targetUnit: string): number;
42
+ /**
43
+ * Get the original CSS value AND unit of a property (excluding inline styles)
44
+ * Returns both value and unit to preserve the original CSS unit (%, rem, em, vh, etc.)
45
+ */
46
+ export declare function getOriginalCSSValueWithUnit(target: Element, prop: string): {
47
+ value: number;
48
+ unit: string;
49
+ };
50
+ /**
51
+ * Convert camelCase to kebab-case
52
+ */
53
+ export declare function camelToKebab(str: string): string;
@@ -27,43 +27,8 @@
27
27
  * Revert:
28
28
  * TextFlapper.revert(charEls);
29
29
  */
30
- import type { FlapConfig } from '../types';
31
- /**
32
- * A single numeric property to interpolate per flap cycle.
33
- * AnimationBuilder populates these from user-provided from/to vars so that
34
- * CSS properties animate once per character-swap cycle instead of once over
35
- * the full animation duration.
36
- */
37
- export interface UserPropEntry {
38
- /** CSS property name or transform shorthand (e.g. 'y', 'opacity', 'rotateX') */
39
- prop: string;
40
- /** Start value (from) */
41
- from: number;
42
- /** End value (to) */
43
- to: number;
44
- /** CSS unit string: 'px', 'deg', '%', or '' for unitless */
45
- unit: string;
46
- /** True when prop is a TransformCache property (routes through setTransformValue) */
47
- isTransform: boolean;
48
- }
49
- /** Lightweight controller for standalone flap() usage (ConnectAI, etc.) */
50
- export interface FlapController {
51
- kill(): void;
52
- readonly finished: Promise<void>;
53
- readonly isComplete: boolean;
54
- }
55
- /**
56
- * Per-character render driver returned by `prepare()`.
57
- * AnimationBuilder uses these to create real Animation objects via Engine.
58
- */
59
- export interface FlapCharDriver {
60
- /** The character element */
61
- readonly el: HTMLElement;
62
- /** Called each frame with progress (0-1). Drives text swaps + visual effects. */
63
- readonly render: (progress: number) => void;
64
- /** Snap to resolved final state. Called on animation complete. */
65
- readonly finalize: () => void;
66
- }
30
+ import type { FlapConfig } from '../../types';
31
+ import type { UserPropEntry, FlapController, FlapCharDriver } from './types';
67
32
  export declare class TextFlapper {
68
33
  /**
69
34
  * Prepare per-character animation drivers for the split-flap effect.
@@ -0,0 +1,21 @@
1
+ /**
2
+ * TextFlapper per-char render function builders.
3
+ *
4
+ * buildCharRender maps linear progress (0-1) to the current visual state and
5
+ * applies the corresponding CSS/text swap; buildCharFinalize snaps to the
6
+ * resolved final state on completion.
7
+ */
8
+ import type { FlapConfig } from '../../types';
9
+ import type { UserPropEntry } from './types';
10
+ /**
11
+ * Build the render callback for a single character.
12
+ *
13
+ * The render function maps a linear progress (0-1) to the current visual state:
14
+ * which cycle, which phase within the cycle, and applies the corresponding
15
+ * CSS properties + text swap.
16
+ *
17
+ * Pre-generates random characters so the animation is deterministic when
18
+ * scrubbed (seek forward/backward produces consistent results).
19
+ */
20
+ export declare function buildCharRender(el: HTMLElement, target: string, cycles: number, charset: string, type: NonNullable<FlapConfig['type']>, perspectivePx: number, continuous: boolean, userProps?: UserPropEntry[]): (progress: number) => void;
21
+ export declare function buildCharFinalize(el: HTMLElement, target: string, type: NonNullable<FlapConfig['type']>, userProps?: UserPropEntry[]): () => void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * TextFlapper constants — charsets, defaults, and the module-level WeakMaps
3
+ * that track per-element state across prepare/render/revert.
4
+ */
5
+ import type { BoardParts } from './types';
6
+ export declare const CHARSETS: Record<string, string>;
7
+ export declare const DEFAULT_SPEED = 80;
8
+ export declare const DEFAULT_PERSPECTIVE = 400;
9
+ export declare const DEFAULT_CYCLES: [number, number];
10
+ export declare const originalContentMap: WeakMap<HTMLElement, string>;
11
+ export declare const boardPartsMap: WeakMap<HTMLElement, BoardParts>;
12
+ /**
13
+ * Tracks elements pinned by `stableWidth: 'container'` so revert() can
14
+ * restore their original `display` and `width` inline styles without
15
+ * clobbering unrelated user-set inline styles.
16
+ */
17
+ export declare const containerPinnedMap: WeakMap<HTMLElement, {
18
+ display: string;
19
+ width: string;
20
+ }>;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * TextFlapper helpers — charset resolution, cycle counting, measurement, and
3
+ * the one-time per-type DOM/style setup that prepare() applies before driving.
4
+ */
5
+ import type { FlapConfig } from '../../types';
6
+ import type { StableWidthMode } from './types';
7
+ /**
8
+ * Walk upward from a character span past any implicit word-wrap spans
9
+ * inserted by TextSplitter ([data-split-word-wrap]). The returned element
10
+ * is the split ROOT — the user-facing container that was passed to split()
11
+ * (e.g. the <a>, <h1>, etc.). This is the element we want to pin in
12
+ * `stableWidth: 'container'` mode, not the intermediate per-word wrappers.
13
+ */
14
+ export declare function findSplitRootContainer(charEl: HTMLElement): HTMLElement | null;
15
+ export declare function resolveCharset(charset: string | undefined): string;
16
+ export declare function getRandomChar(charset: string): string;
17
+ export declare function resolveCycleCount(cycles: FlapConfig['cycles']): number;
18
+ export declare function isWhitespaceChar(char: string): boolean;
19
+ /**
20
+ * Record original textContent for revert, return normalised target char.
21
+ */
22
+ export declare function recordAndNormalise(el: HTMLElement): string;
23
+ /**
24
+ * One-time setup styles required by each animation type.
25
+ */
26
+ /**
27
+ * Measure the widest glyph in `chars` using the same rendering context as
28
+ * `referenceEl` (font, size, weight, letter-spacing etc. all inherit).
29
+ *
30
+ * Used by `stableWidth` to size each char cell (or container) wide enough
31
+ * for BOTH the target text AND the random charset pool. Without this,
32
+ * cycling from a narrow Latin target ("Getting started") through a wider
33
+ * charset (katakana, blocks, CJK) causes per-cycle width jumps since the
34
+ * measurement was only sized for the narrow target.
35
+ *
36
+ * Returns the widest measured width AND the character that produced it —
37
+ * the character is used by `stableWidth: 'container'` to simulate the
38
+ * widest flap state in-place for a precise (subpixel-aware) measurement
39
+ * of the ROOT container's full flap width.
40
+ *
41
+ * Measurement strategy: one absolutely-positioned invisible wrapper holds
42
+ * one inline-block child per unique char. Appended once, all widths are
43
+ * read with getBoundingClientRect() after a single reflow, then the
44
+ * wrapper is removed.
45
+ */
46
+ export declare function measureMaxCharsetWidth(referenceEl: HTMLElement, chars: string): {
47
+ width: number;
48
+ char: string;
49
+ };
50
+ export declare function applyTypeSetup(charElements: HTMLElement[], type: FlapConfig['type'], perspectivePx?: number, styledBoard?: boolean, stableWidth?: StableWidthMode, preserveWhitespaceCells?: boolean, charset?: string): void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * TextFlapper barrel — preserves the public import path `../utils/TextFlapper`.
3
+ *
4
+ * Re-exports the exact public surface the original single-file module had:
5
+ * - class `TextFlapper`
6
+ * - types `UserPropEntry`, `FlapController`, `FlapCharDriver`
7
+ *
8
+ * The SDKRegistry self-registration side effect lives in `./TextFlapper`
9
+ * (co-located with the class), so importing this barrel still registers the
10
+ * implementation on SDKRegistry at module load — the behaviour the SDK entry
11
+ * relies on. It is intentionally NOT a separate bare `import './register'`:
12
+ * the chunked SDK bundler does not inline bare side-effect imports and would
13
+ * leak a raw `import` statement into the standalone bundle.
14
+ */
15
+ export { TextFlapper } from './TextFlapper';
16
+ export type { UserPropEntry, FlapController, FlapCharDriver } from './types';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * TextFlapper types — public driver/controller interfaces plus internal
3
+ * structural types shared across the TextFlapper parts.
4
+ */
5
+ /**
6
+ * A single numeric property to interpolate per flap cycle.
7
+ * AnimationBuilder populates these from user-provided from/to vars so that
8
+ * CSS properties animate once per character-swap cycle instead of once over
9
+ * the full animation duration.
10
+ */
11
+ export interface UserPropEntry {
12
+ /** CSS property name or transform shorthand (e.g. 'y', 'opacity', 'rotateX') */
13
+ prop: string;
14
+ /** Start value (from) */
15
+ from: number;
16
+ /** End value (to) */
17
+ to: number;
18
+ /** CSS unit string: 'px', 'deg', '%', or '' for unitless */
19
+ unit: string;
20
+ /** True when prop is a TransformCache property (routes through setTransformValue) */
21
+ isTransform: boolean;
22
+ }
23
+ /** Lightweight controller for standalone flap() usage (ConnectAI, etc.) */
24
+ export interface FlapController {
25
+ kill(): void;
26
+ readonly finished: Promise<void>;
27
+ readonly isComplete: boolean;
28
+ }
29
+ /**
30
+ * Per-character render driver returned by `prepare()`.
31
+ * AnimationBuilder uses these to create real Animation objects via Engine.
32
+ */
33
+ export interface FlapCharDriver {
34
+ /** The character element */
35
+ readonly el: HTMLElement;
36
+ /** Called each frame with progress (0-1). Drives text swaps + visual effects. */
37
+ readonly render: (progress: number) => void;
38
+ /** Snap to resolved final state. Called on animation complete. */
39
+ readonly finalize: () => void;
40
+ }
41
+ /**
42
+ * Board type DOM structure — real Solari departure board effect.
43
+ *
44
+ * A single two-sided flap rotates 0° → -180° around the split line:
45
+ * FRONT = OLD char's top half (visible 0° to ~-90°)
46
+ * BACK = NEW char's bottom half (visible ~-90° to -180°, lands at bottom)
47
+ *
48
+ * Two static halves sit behind the flap:
49
+ * staticTop = NEW char's top (revealed as flap falls away)
50
+ * staticBottom = OLD char's bottom (covered by flapBack when it lands)
51
+ *
52
+ * The flap uses a wrapper with transform-style: preserve-3d (no overflow).
53
+ * Front and back children each have overflow:hidden + backface-visibility:hidden.
54
+ *
55
+ * A persistent split line sits at z:3 above everything for realism.
56
+ * A dynamic shadow overlay on the bottom half adds depth during rotation.
57
+ *
58
+ * Layout:
59
+ * container (el, perspective)
60
+ * ├─ staticTop (top:0, h:50%, overflow:hidden, z:1) → text (NEW top)
61
+ * ├─ staticBottom (top:50%, h:50%, overflow:hidden, z:1) → text (OLD→NEW bottom)
62
+ * ├─ flapWrap (top:0, h:50%, transformOrigin:center bottom, preserve-3d, z:2)
63
+ * │ ├─ flapFront (h:100%, overflow:hidden, backface:hidden) → text (OLD top)
64
+ * │ └─ flapBack (h:100%, overflow:hidden, backface:hidden, rotateX:180°) → text (NEW bottom)
65
+ * └─ splitLine (top:50%, h:1px, z:3) — persistent center divider
66
+ */
67
+ export interface BoardParts {
68
+ /** Static NEW char top half (background, revealed as flap falls) */
69
+ staticTop: HTMLElement;
70
+ /** Static bottom half (OLD char initially, switches to NEW at midpoint) */
71
+ staticBottom: HTMLElement;
72
+ /** Flap wrapper — rotates 0°→-180°, holds front/back faces */
73
+ flapWrap: HTMLElement;
74
+ /** Front face — OLD char top (visible 0° to -90°) */
75
+ flapFront: HTMLElement;
76
+ /** Back face — NEW char bottom (visible -90° to -180°) */
77
+ flapBack: HTMLElement;
78
+ /** Persistent split line across the center */
79
+ splitLine: HTMLElement;
80
+ /** Dynamic shadow overlay on the bottom half — opacity driven by flap angle */
81
+ shadow: HTMLElement;
82
+ /** Inner text spans */
83
+ staticTopText: HTMLElement;
84
+ staticBottomText: HTMLElement;
85
+ flapFrontText: HTMLElement;
86
+ flapBackText: HTMLElement;
87
+ }
88
+ /** Normalised internal form of FlapConfig.stableWidth */
89
+ export type StableWidthMode = 'none' | 'cells' | 'container';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * TextSplitter - Split text into animatable elements
3
+ *
4
+ * Splits text content into chars, words, or lines wrapped in spans.
5
+ * Supports nested splitting (e.g., 'chars,words' = words containing char spans).
6
+ *
7
+ * Preserves existing element structure — inline elements like
8
+ * `<span class="accent">word</span>` keep their wrapper and styling.
9
+ * Only text nodes are replaced with split spans.
10
+ *
11
+ * Usage:
12
+ * const elements = TextSplitter.split(element, 'chars');
13
+ * const elements = TextSplitter.split(element, 'words');
14
+ * const elements = TextSplitter.split(element, 'chars,words');
15
+ *
16
+ * Revert:
17
+ * TextSplitter.revert(element);
18
+ * TextSplitter.revert(element, consumer); // reference-counted
19
+ */
20
+ import type { SplitType, SplitOptions, SplitResult } from './types';
21
+ export declare class TextSplitter {
22
+ /**
23
+ * Split text content into animatable elements.
24
+ *
25
+ * When `options.consumer` is provided the split is reference-counted.
26
+ * A second consumer on the same element will have the DOM upgraded
27
+ * (chars nested inside existing word spans, or word-wraps promoted to
28
+ * word spans) rather than reverted and rebuilt, so the first consumer's
29
+ * DOM references remain valid.
30
+ *
31
+ * Without a consumer token the function behaves exactly like before:
32
+ * any existing split is reverted and a fresh split is performed.
33
+ */
34
+ static split(element: Element, type: SplitType, options?: SplitOptions): HTMLElement[];
35
+ /**
36
+ * Revert element to original content.
37
+ *
38
+ * When `consumer` is provided the revert is reference-counted: the DOM is
39
+ * only restored once all registered consumers have released via this method.
40
+ * If the consumer is the last one (or no consumer tracking is active), the
41
+ * element's innerHTML is restored to the pre-split original immediately.
42
+ *
43
+ * Without `consumer` (legacy / forced revert), the DOM is restored regardless
44
+ * of how many consumers are still registered. All consumer refs are cleared.
45
+ */
46
+ static revert(element: Element, consumer?: object): boolean;
47
+ /**
48
+ * Check if element has been split
49
+ */
50
+ static isSplit(element: Element): boolean;
51
+ /**
52
+ * Get split result for an element
53
+ */
54
+ static getResult(element: Element): SplitResult | undefined;
55
+ }