@pixerate/flair 0.13.1 → 0.15.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.
@@ -0,0 +1,82 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+ import { V as VectorEffect$1 } from '../types-9J6NWaN3.js';
4
+ import { C as ControlValue } from '../controls-Syr6drzO.js';
5
+ import { S as SvgMotion, a as SvgTrigger, C as Clock, b as SvgPlayer } from '../svg-XugR-6Ec.js';
6
+
7
+ /** When to leave the still in place of the animation. */
8
+ type ReducedMotion = "user" | "always" | "never";
9
+ interface SvgPlayerViewProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
10
+ /** The picture and its animations. */
11
+ motion: SvgMotion;
12
+ /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */
13
+ loop?: boolean;
14
+ /** Start playing at once (default `true`). */
15
+ autoplay?: boolean;
16
+ /** Playback speed (default 1). */
17
+ speed?: number;
18
+ /** Ms to rest on the last frame before looping (default 600). */
19
+ hold?: number;
20
+ /** Loop length in ms (default: the picture's own, plus `hold`). */
21
+ duration?: number;
22
+ /** Step start times in ms, for pictures that change on steps. */
23
+ steps?: readonly number[];
24
+ /** Plays in answer to the pointer (see `SvgTrigger`). */
25
+ trigger?: SvgTrigger;
26
+ /** Pause while scrolled out of view. */
27
+ pauseWhenHidden?: boolean;
28
+ /** Wait to start until scrolled into view. */
29
+ playOnEnter?: boolean;
30
+ /** Hide the picture while paused or reset. */
31
+ hideWhenPaused?: boolean;
32
+ /** Time source (default animation frames); a `ManualClock` steps frames yourself. */
33
+ clock?: Clock;
34
+ /** Called with the id of the innermost element with an id that is clicked. */
35
+ onPick?: (id: string) => void;
36
+ /** Called with the player once it is made, and `undefined` when it is disposed. */
37
+ onPlayer?: (player: SvgPlayer | undefined) => void;
38
+ /**
39
+ * The still shown before the player starts (server render, hydration) and
40
+ * instead of it under reduced motion: `"last"` (default; the rest frame,
41
+ * `"first"` for a hover or press trigger), `"first"` or a time in ms.
42
+ */
43
+ poster?: "first" | "last" | number;
44
+ /**
45
+ * `"user"` (default) shows the still when the visitor prefers reduced
46
+ * motion, `"always"` always does, `"never"` always plays.
47
+ */
48
+ reducedMotion?: ReducedMotion;
49
+ /**
50
+ * Size it as a word in a line of text: `display: inline-block` with the
51
+ * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy
52
+ * sans; pass a CSS length for another). Meant for pictures framed from cap
53
+ * height to baseline, like drip text's `inline`. Otherwise it is a block
54
+ * as wide as its container with the viewBox's aspect ratio.
55
+ */
56
+ inline?: boolean | string;
57
+ }
58
+ /**
59
+ * Plays an {@link SvgMotion}: a still on the server and before mount, then
60
+ * an `SvgPlayer` (remade when the motion or its options change, disposed on
61
+ * unmount). Pass a memoised `motion`: a new object each render restarts it.
62
+ */
63
+ declare function SvgPlayerView(props: SvgPlayerViewProps): react.JSX.Element;
64
+ interface VectorEffectProps extends Omit<SvgPlayerViewProps, "motion" | "id"> {
65
+ /** A vector effect's catalog id (`"drip-text"`), or the effect itself. */
66
+ id: string | VectorEffect$1;
67
+ /** Control values; missing ones take the preset's, then the defaults. */
68
+ values?: Record<string, ControlValue>;
69
+ /** A preset's name to start from. */
70
+ preset?: string;
71
+ /** The host element's own `id`. */
72
+ elementId?: string;
73
+ }
74
+ /**
75
+ * Plays a catalog vector effect: `<VectorEffect id="drip-text" values={{ word: "Slop" }} loop={false} />`.
76
+ * Its `hold` and `trigger` are used unless given. With an `inline` control
77
+ * set (drip text's), it is sized as a word in the line (see `inline`).
78
+ * Throws when `id` is not a vector effect.
79
+ */
80
+ declare function VectorEffect(props: VectorEffectProps): react.JSX.Element;
81
+
82
+ export { type ReducedMotion, SvgMotion, SvgPlayer, SvgPlayerView, type SvgPlayerViewProps, SvgTrigger, VectorEffect, type VectorEffectProps };
@@ -0,0 +1,132 @@
1
+ import {
2
+ effectDefaults,
3
+ getEffect,
4
+ renderVectorEffect
5
+ } from "../chunk-KWNFZ5NC.js";
6
+ import {
7
+ svgFrameMarkup,
8
+ svgViewBox
9
+ } from "../chunk-DVVQRZJ5.js";
10
+ import {
11
+ SvgPlayer
12
+ } from "../chunk-4RGHZWQC.js";
13
+ import "../chunk-OHW7UG6D.js";
14
+ import "../chunk-EHDJ3CPJ.js";
15
+ import "../chunk-WC3VADZC.js";
16
+ import "../chunk-WMZQCUFJ.js";
17
+
18
+ // src/react/index.tsx
19
+ import {
20
+ useEffect,
21
+ useLayoutEffect,
22
+ useMemo,
23
+ useRef
24
+ } from "react";
25
+ import { jsx, jsxs } from "react/jsx-runtime";
26
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
27
+ var CAP_HEIGHT = "0.72em";
28
+ function prefersReducedMotion() {
29
+ return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
30
+ }
31
+ function hostStyle(svg, inline, style) {
32
+ const box = svgViewBox(svg);
33
+ const aspectRatio = box ? `${box[2]} / ${box[3]}` : void 0;
34
+ const base = inline ? { display: "inline-block", height: typeof inline === "string" ? inline : CAP_HEIGHT, aspectRatio } : { display: "block", aspectRatio };
35
+ return { position: "relative", ...base, ...style };
36
+ }
37
+ var LAYER = { position: "absolute", inset: 0, display: "block" };
38
+ function SvgPlayerView(props) {
39
+ const {
40
+ motion,
41
+ loop,
42
+ autoplay,
43
+ speed,
44
+ hold,
45
+ duration,
46
+ steps,
47
+ trigger,
48
+ pauseWhenHidden,
49
+ playOnEnter,
50
+ hideWhenPaused,
51
+ clock,
52
+ onPick,
53
+ onPlayer,
54
+ poster,
55
+ reducedMotion = "user",
56
+ inline,
57
+ style,
58
+ ...rest
59
+ } = props;
60
+ const still = useRef(null);
61
+ const mount = useRef(null);
62
+ const player = useRef(void 0);
63
+ const callbacks = useRef({ onPick, onPlayer });
64
+ callbacks.current = { onPick, onPlayer };
65
+ const at = poster ?? (trigger === "hover" || trigger === "press" ? "first" : "last");
66
+ const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? "", [motion, at, steps]);
67
+ const stepsKey = steps?.join(",");
68
+ useIsomorphicLayoutEffect(() => {
69
+ const host = mount.current;
70
+ if (!host) return;
71
+ if (reducedMotion === "always" || reducedMotion === "user" && prefersReducedMotion()) return;
72
+ const p = new SvgPlayer(host, motion, {
73
+ loop,
74
+ autoplay,
75
+ speed,
76
+ hold,
77
+ duration,
78
+ steps,
79
+ trigger,
80
+ pauseWhenHidden,
81
+ playOnEnter,
82
+ hideWhenPaused,
83
+ clock,
84
+ onPick: (id) => callbacks.current.onPick?.(id)
85
+ });
86
+ player.current = p;
87
+ if (still.current) still.current.style.display = "none";
88
+ callbacks.current.onPlayer?.(p);
89
+ return () => {
90
+ p.dispose();
91
+ player.current = void 0;
92
+ if (still.current) still.current.style.display = "block";
93
+ callbacks.current.onPlayer?.(void 0);
94
+ };
95
+ }, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden, playOnEnter, hideWhenPaused, clock, reducedMotion]);
96
+ useEffect(() => {
97
+ player.current?.setSpeed(speed ?? 1);
98
+ }, [speed]);
99
+ return /* @__PURE__ */ jsxs("span", { ...rest, style: hostStyle(motion.svg, inline, style), children: [
100
+ /* @__PURE__ */ jsx("span", { ref: still, "aria-hidden": "true", style: LAYER, dangerouslySetInnerHTML: { __html: markup } }),
101
+ /* @__PURE__ */ jsx("span", { ref: mount, style: LAYER })
102
+ ] });
103
+ }
104
+ function VectorEffect(props) {
105
+ const { id, values, preset, elementId, ...rest } = props;
106
+ const effect = typeof id === "string" ? getEffect(id) : id;
107
+ if (!effect || effect.kind !== "vector")
108
+ throw new Error(`"${typeof id === "string" ? id : effect?.id}" is not a vector effect; pick one from findEffects({ kind: "vector" })`);
109
+ const valuesKey = JSON.stringify(values ?? {});
110
+ const motion = useMemo(
111
+ () => renderVectorEffect(effect, effectDefaults(effect, { preset, values })),
112
+ // Values by content, so an inline object literal does not restart it.
113
+ // eslint-disable-next-line react-hooks/exhaustive-deps
114
+ [effect, preset, valuesKey]
115
+ );
116
+ return /* @__PURE__ */ jsx(
117
+ SvgPlayerView,
118
+ {
119
+ id: elementId,
120
+ motion,
121
+ hold: effect.hold,
122
+ trigger: effect.trigger,
123
+ inline: values?.inline === true,
124
+ ...rest
125
+ }
126
+ );
127
+ }
128
+ export {
129
+ SvgPlayerView,
130
+ VectorEffect
131
+ };
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["/**\n * React components for Flair's vector effects: `<VectorEffect>` plays a\n * catalog effect by id, `<SvgPlayerView>` plays any {@link SvgMotion}.\n *\n * Both render a still of the picture on the server (its last frame, by\n * default), so a page hydrates with the picture in place rather than a blank\n * box that jumps; the live `SvgPlayer` takes over after mount and is disposed\n * on unmount. Under `prefers-reduced-motion: reduce` the still stays.\n *\n * Needs React 18 or later (an optional peer dependency of the package).\n *\n * @packageDocumentation\n */\n\nimport {\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { getEffect, effectDefaults } from \"../catalog/index.js\";\nimport { renderVectorEffect, type VectorEffect as VectorEffectDefinition } from \"../catalog/types.js\";\nimport type { ControlValue } from \"../shader/controls.js\";\nimport type { Clock } from \"../time/clock.js\";\nimport type { SvgMotion } from \"../vector/motion.js\";\nimport { svgFrameMarkup, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\n\n// Layout effects swap the still for the player before paint; on a server neither runs.\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/** Cap height of a heavy sans, as a share of its font size: what an inline word is framed to. */\nconst CAP_HEIGHT = \"0.72em\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\nexport interface SvgPlayerViewProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The picture and its animations. */\n motion: SvgMotion;\n /** Start again at the end (default `true`); `false` plays once and stays on the last frame. */\n loop?: boolean;\n /** Start playing at once (default `true`). */\n autoplay?: boolean;\n /** Playback speed (default 1). */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600). */\n hold?: number;\n /** Loop length in ms (default: the picture's own, plus `hold`). */\n duration?: number;\n /** Step start times in ms, for pictures that change on steps. */\n steps?: readonly number[];\n /** Plays in answer to the pointer (see `SvgTrigger`). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /** Wait to start until scrolled into view. */\n playOnEnter?: boolean;\n /** Hide the picture while paused or reset. */\n hideWhenPaused?: boolean;\n /** Time source (default animation frames); a `ManualClock` steps frames yourself. */\n clock?: Clock;\n /** Called with the id of the innermost element with an id that is clicked. */\n onPick?: (id: string) => void;\n /** Called with the player once it is made, and `undefined` when it is disposed. */\n onPlayer?: (player: SvgPlayer | undefined) => void;\n /**\n * The still shown before the player starts (server render, hydration) and\n * instead of it under reduced motion: `\"last\"` (default; the rest frame,\n * `\"first\"` for a hover or press trigger), `\"first\"` or a time in ms.\n */\n poster?: \"first\" | \"last\" | number;\n /**\n * `\"user\"` (default) shows the still when the visitor prefers reduced\n * motion, `\"always\"` always does, `\"never\"` always plays.\n */\n reducedMotion?: ReducedMotion;\n /**\n * Size it as a word in a line of text: `display: inline-block` with the\n * viewBox's aspect ratio and `height` 0.72em (the cap height of a heavy\n * sans; pass a CSS length for another). Meant for pictures framed from cap\n * height to baseline, like drip text's `inline`. Otherwise it is a block\n * as wide as its container with the viewBox's aspect ratio.\n */\n inline?: boolean | string;\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The host's size from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, style?: CSSProperties): CSSProperties {\n const box = svgViewBox(svg);\n const aspectRatio = box ? `${box[2]} / ${box[3]}` : undefined;\n const base: CSSProperties = inline\n ? { display: \"inline-block\", height: typeof inline === \"string\" ? inline : CAP_HEIGHT, aspectRatio }\n : { display: \"block\", aspectRatio };\n return { position: \"relative\", ...base, ...style };\n}\n\nconst LAYER: CSSProperties = { position: \"absolute\", inset: 0, display: \"block\" };\n\n/**\n * Plays an {@link SvgMotion}: a still on the server and before mount, then\n * an `SvgPlayer` (remade when the motion or its options change, disposed on\n * unmount). Pass a memoised `motion`: a new object each render restarts it.\n */\nexport function SvgPlayerView(props: SvgPlayerViewProps) {\n const {\n motion,\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter,\n hideWhenPaused,\n clock,\n onPick,\n onPlayer,\n poster,\n reducedMotion = \"user\",\n inline,\n style,\n ...rest\n } = props;\n const still = useRef<HTMLSpanElement>(null);\n const mount = useRef<HTMLSpanElement>(null);\n const player = useRef<SvgPlayer | undefined>(undefined);\n // Callbacks change every render; the player calls the latest without being remade.\n const callbacks = useRef({ onPick, onPlayer });\n callbacks.current = { onPick, onPlayer };\n\n const at = poster ?? (trigger === \"hover\" || trigger === \"press\" ? \"first\" : \"last\");\n const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? \"\", [motion, at, steps]);\n const stepsKey = steps?.join(\",\");\n\n useIsomorphicLayoutEffect(() => {\n const host = mount.current;\n if (!host) return;\n if (reducedMotion === \"always\" || (reducedMotion === \"user\" && prefersReducedMotion())) return;\n const p = new SvgPlayer(host, motion, {\n loop,\n autoplay,\n speed,\n hold,\n duration,\n steps,\n trigger,\n pauseWhenHidden,\n playOnEnter,\n hideWhenPaused,\n clock,\n onPick: (id) => callbacks.current.onPick?.(id),\n });\n player.current = p;\n // Set directly, not through props, so React leaves it be on later renders.\n if (still.current) still.current.style.display = \"none\";\n callbacks.current.onPlayer?.(p);\n return () => {\n p.dispose();\n player.current = undefined;\n if (still.current) still.current.style.display = \"block\";\n callbacks.current.onPlayer?.(undefined);\n };\n // `speed` is applied below without remaking; `steps` by value.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden, playOnEnter, hideWhenPaused, clock, reducedMotion]);\n\n useEffect(() => {\n player.current?.setSpeed(speed ?? 1);\n }, [speed]);\n\n return (\n <span {...rest} style={hostStyle(motion.svg, inline, style)}>\n <span ref={still} aria-hidden=\"true\" style={LAYER} dangerouslySetInnerHTML={{ __html: markup }} />\n <span ref={mount} style={LAYER} />\n </span>\n );\n}\n\nexport interface VectorEffectProps extends Omit<SvgPlayerViewProps, \"motion\" | \"id\"> {\n /** A vector effect's catalog id (`\"drip-text\"`), or the effect itself. */\n id: string | VectorEffectDefinition;\n /** Control values; missing ones take the preset's, then the defaults. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n /** The host element's own `id`. */\n elementId?: string;\n}\n\n/**\n * Plays a catalog vector effect: `<VectorEffect id=\"drip-text\" values={{ word: \"Slop\" }} loop={false} />`.\n * Its `hold` and `trigger` are used unless given. With an `inline` control\n * set (drip text's), it is sized as a word in the line (see `inline`).\n * Throws when `id` is not a vector effect.\n */\nexport function VectorEffect(props: VectorEffectProps) {\n const { id, values, preset, elementId, ...rest } = props;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(`\"${typeof id === \"string\" ? id : effect?.id}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" })`);\n const valuesKey = JSON.stringify(values ?? {});\n const motion = useMemo(\n () => renderVectorEffect(effect, effectDefaults(effect, { preset, values })),\n // Values by content, so an inline object literal does not restart it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [effect, preset, valuesKey],\n );\n return (\n <SvgPlayerView\n id={elementId}\n motion={motion}\n hold={effect.hold}\n trigger={effect.trigger}\n inline={values?.inline === true}\n {...rest}\n />\n );\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n"],"mappings":"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","names":[]}
@@ -265,7 +265,10 @@ interface SvgPlayerOptions extends SvgViewOptions {
265
265
  clock?: Clock;
266
266
  /** Start playing at once (default `true`). */
267
267
  autoplay?: boolean;
268
- /** Start again at the end (default `true`). */
268
+ /**
269
+ * Start again at the end (default `true`). When `false` it plays once and
270
+ * stays on the last frame (hidden instead with `hideWhenPaused`).
271
+ */
269
272
  loop?: boolean;
270
273
  /** Loop length in ms (default: the picture's own, see {@link svgDuration}, plus `hold`). */
271
274
  duration?: number;
@@ -379,4 +382,4 @@ interface AnimateCountOptions {
379
382
  */
380
383
  declare function animateCount(options: AnimateCountOptions): () => void;
381
384
 
382
- export { type AnimateCountOptions as A, svgMotionIssues as B, type Clock as C, type DataKey as D, EASES as E, svgOpeningTag as F, svgView as G, writtenText as H, ManualClock as M, type SvgMotion as S, type SvgTrigger as a, type EaseName as b, SCRAMBLE_MS as c, STEP_MS as d, SVG_PICTURE as e, type SvgAnimation as f, type SvgBox as g, type SvgElementFrame as h, SvgPlayer as i, type SvgPlayerOptions as j, type SvgView as k, type SvgViewOptions as l, animateCount as m, defaultDataKey as n, formatCount as o, jitter as p, lerpAttr as q, lerpData as r, pageHidden as s, parseSvg as t, realClock as u, stepData as v, stepProgress as w, stepStart as x, svgDuration as y, svgFrame as z };
385
+ export { type AnimateCountOptions as A, svgMotionIssues as B, type Clock as C, type DataKey as D, EASES as E, svgOpeningTag as F, svgView as G, writtenText as H, ManualClock as M, type SvgMotion as S, type SvgTrigger as a, SvgPlayer as b, type EaseName as c, SCRAMBLE_MS as d, STEP_MS as e, SVG_PICTURE as f, type SvgAnimation as g, type SvgBox as h, type SvgElementFrame as i, type SvgPlayerOptions as j, type SvgView as k, type SvgViewOptions as l, animateCount as m, defaultDataKey as n, formatCount as o, jitter as p, lerpAttr as q, lerpData as r, pageHidden as s, parseSvg as t, realClock as u, stepData as v, stepProgress as w, stepStart as x, svgDuration as y, svgFrame as z };
@@ -1,5 +1,5 @@
1
1
  import { C as ControlValue, a as ControlDef } from './controls-Syr6drzO.js';
2
- import { S as SvgMotion, a as SvgTrigger } from './svg-DNEreR2p.js';
2
+ import { S as SvgMotion, a as SvgTrigger } from './svg-XugR-6Ec.js';
3
3
 
4
4
  /** What an effect is for, used to group the catalog. */
5
5
  type EffectCategory = "gradient" | "organic" | "pattern" | "light" | "space" | "retro" | "image" | "overlay" | "text" | "morph" | "interaction" | "pyrotechnics" | "timewarp" | "melt" | "electric" | "transition" | "loader" | "data" | "icon";
@@ -111,4 +111,4 @@ declare function colorControl(name: string, hex: string, label?: string): Vector
111
111
  /** A number control for vector effects. */
112
112
  declare function numberControl(name: string, value: number, min: number, max: number, label?: string, step?: number): VectorControl;
113
113
 
114
- export { type EffectDefinition as E, type ShaderEffect as S, VECTOR_CONTROL_TYPES as V, type EffectCategory as a, EFFECT_CATEGORIES as b, type EffectPreset as c, type VectorControl as d, type VectorEffect as e, colorControl as f, vectorControlIssues as g, vectorControls as h, vectorEffectControls as i, numberControl as n, renderVectorEffect as r, vectorControlDef as v };
114
+ export { type EffectDefinition as E, type ShaderEffect as S, type VectorEffect as V, type EffectCategory as a, EFFECT_CATEGORIES as b, type EffectPreset as c, VECTOR_CONTROL_TYPES as d, type VectorControl as e, colorControl as f, vectorControlIssues as g, vectorControls as h, vectorEffectControls as i, numberControl as n, renderVectorEffect as r, vectorControlDef as v };
@@ -1,5 +1,5 @@
1
- import { g as SvgBox } from '../svg-DNEreR2p.js';
2
- export { A as AnimateCountOptions, D as DataKey, E as EASES, b as EaseName, c as SCRAMBLE_MS, d as STEP_MS, e as SVG_PICTURE, f as SvgAnimation, h as SvgElementFrame, S as SvgMotion, i as SvgPlayer, j as SvgPlayerOptions, a as SvgTrigger, k as SvgView, l as SvgViewOptions, m as animateCount, n as defaultDataKey, o as formatCount, q as lerpAttr, r as lerpData, t as parseSvg, v as stepData, w as stepProgress, x as stepStart, y as svgDuration, z as svgFrame, B as svgMotionIssues, F as svgOpeningTag, G as svgView, H as writtenText } from '../svg-DNEreR2p.js';
1
+ import { h as SvgBox, S as SvgMotion } from '../svg-XugR-6Ec.js';
2
+ export { A as AnimateCountOptions, D as DataKey, E as EASES, c as EaseName, d as SCRAMBLE_MS, e as STEP_MS, f as SVG_PICTURE, g as SvgAnimation, i as SvgElementFrame, b as SvgPlayer, j as SvgPlayerOptions, a as SvgTrigger, k as SvgView, l as SvgViewOptions, m as animateCount, n as defaultDataKey, o as formatCount, q as lerpAttr, r as lerpData, t as parseSvg, v as stepData, w as stepProgress, x as stepStart, y as svgDuration, z as svgFrame, B as svgMotionIssues, F as svgOpeningTag, G as svgView, H as writtenText } from '../svg-XugR-6Ec.js';
3
3
 
4
4
  /**
5
5
  * Shape morphing: any SVG paths turned into paths with the same structure, so
@@ -28,6 +28,38 @@ declare function morphSequence(paths: readonly string[], options?: MorphOptions)
28
28
  */
29
29
  declare function morphPaths(from: string, to: string, options?: MorphOptions): [string, string];
30
30
 
31
+ /**
32
+ * An {@link SvgMotion}'s frame as markup, with no DOM: for server rendering
33
+ * (a still that hydrates without a blank flash), reduced motion and other
34
+ * places a live player is not wanted.
35
+ */
36
+
37
+ /** A `viewBox` attribute's box, or undefined when it has none or it is malformed. */
38
+ declare function parseViewBox(value: string | null | undefined): SvgBox | undefined;
39
+ /** The `viewBox` on an SVG's opening tag (see {@link parseViewBox}). */
40
+ declare function svgViewBox(markup: string): SvgBox | undefined;
41
+ interface SvgFrameMarkupOptions {
42
+ /**
43
+ * The time shown, in ms: a number, `"first"` (0) or `"last"` (the end of
44
+ * its last change, see `svgDuration`; the default).
45
+ */
46
+ at?: number | "first" | "last";
47
+ /** Step start times in ms, as a player's `steps` (default none). */
48
+ steps?: readonly number[];
49
+ }
50
+ /**
51
+ * The picture of an {@link SvgMotion} at a moment, as `<svg>` markup made safe
52
+ * the way `parseSvg` makes it (no scripts, handlers, `<foreignObject>` or
53
+ * outside links), with no DOM needed: render it on a server, show it under
54
+ * `prefers-reduced-motion`, or anywhere a still will do. It is sized to fill
55
+ * its container, as `SvgPlayer` draws it.
56
+ *
57
+ * It matches the player's frame except where that needs layout: a change
58
+ * `along` a path, and a `view` that frames an element by id, stay where the
59
+ * markup puts them. Undefined when `motion.svg` is not an `<svg>`.
60
+ */
61
+ declare function svgFrameMarkup(motion: SvgMotion, options?: SvgFrameMarkupOptions): string | undefined;
62
+
31
63
  type BoxFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
32
64
  type BoxAlignment = 'topLeft' | 'topCenter' | 'topRight' | 'centerLeft' | 'center' | 'centerRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
33
65
  interface FittedBoxResult {
@@ -81,4 +113,4 @@ interface FormatUnitOptions {
81
113
  */
82
114
  declare function formatUnit(value: number, format?: UnitFormat, options?: FormatUnitOptions): string;
83
115
 
84
- export { type BoxAlignment, type BoxFit, type FittedBoxResult, type FormatUnitOptions, type MorphOptions, type StackLayoutOptions, SvgBox, type UnitFormat, computeBoxFit, formatUnit, layoutStack, morphPaths, morphSequence, remap, stepVariant };
116
+ export { type BoxAlignment, type BoxFit, type FittedBoxResult, type FormatUnitOptions, type MorphOptions, type StackLayoutOptions, SvgBox, type SvgFrameMarkupOptions, SvgMotion, type UnitFormat, computeBoxFit, formatUnit, layoutStack, morphPaths, morphSequence, parseViewBox, remap, stepVariant, svgFrameMarkup, svgViewBox };
@@ -4,7 +4,12 @@ import {
4
4
  layoutStack,
5
5
  remap,
6
6
  stepVariant
7
- } from "../chunk-H355R6U4.js";
7
+ } from "../chunk-VP6IEWIK.js";
8
+ import {
9
+ parseViewBox,
10
+ svgFrameMarkup,
11
+ svgViewBox
12
+ } from "../chunk-DVVQRZJ5.js";
8
13
  import {
9
14
  EASES,
10
15
  SCRAMBLE_MS,
@@ -26,7 +31,7 @@ import {
26
31
  svgOpeningTag,
27
32
  svgView,
28
33
  writtenText
29
- } from "../chunk-UJDQJK2O.js";
34
+ } from "../chunk-4RGHZWQC.js";
30
35
  import {
31
36
  morphPaths,
32
37
  morphSequence
@@ -49,6 +54,7 @@ export {
49
54
  morphPaths,
50
55
  morphSequence,
51
56
  parseSvg,
57
+ parseViewBox,
52
58
  remap,
53
59
  stepData,
54
60
  stepProgress,
@@ -56,9 +62,11 @@ export {
56
62
  stepVariant,
57
63
  svgDuration,
58
64
  svgFrame,
65
+ svgFrameMarkup,
59
66
  svgMotionIssues,
60
67
  svgOpeningTag,
61
68
  svgView,
69
+ svgViewBox,
62
70
  writtenText
63
71
  };
64
72
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/flair",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "Web visual effects and motion graphics: WebGL2 shader effects, animated vector effects, a catalog of ready-made effects, and a UI-agnostic, agent-ready effect editor.",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -38,6 +38,10 @@
38
38
  "types": "./dist/vector/index.d.ts",
39
39
  "import": "./dist/vector/index.js"
40
40
  },
41
+ "./react": {
42
+ "types": "./dist/react/index.d.ts",
43
+ "import": "./dist/react/index.js"
44
+ },
41
45
  "./node": {
42
46
  "types": "./dist/node.d.ts",
43
47
  "import": "./dist/node.js"
@@ -57,25 +61,34 @@
57
61
  "effects",
58
62
  "generative",
59
63
  "editor",
60
- "ai-agents"
64
+ "ai-agents",
65
+ "react"
61
66
  ],
62
67
  "dependencies": {
63
68
  "commander": "^12.0.0",
64
69
  "gifenc": "^1.0.3"
65
70
  },
66
71
  "peerDependencies": {
67
- "@pixerate/editor": ">=0.29.0 <1"
72
+ "@pixerate/editor": ">=0.29.0 <1",
73
+ "react": ">=18"
68
74
  },
69
75
  "peerDependenciesMeta": {
70
76
  "@pixerate/editor": {
71
77
  "optional": true
78
+ },
79
+ "react": {
80
+ "optional": true
72
81
  }
73
82
  },
74
83
  "devDependencies": {
75
84
  "@changesets/cli": "^2.27.12",
76
85
  "@pixerate/editor": "^0.29.0",
77
86
  "@types/node": "^24.19.2",
87
+ "@types/react": "^19.3.0",
88
+ "@types/react-dom": "^19.3.0",
78
89
  "jsdom": "^30.1.3",
90
+ "react": "^19.3.1",
91
+ "react-dom": "^19.3.1",
79
92
  "tsup": "^8.5",
80
93
  "typescript": "^5.9",
81
94
  "vitest": "^4.1.11"