@pixerate/flair 0.16.0 → 0.17.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.
- package/CHANGELOG.md +16 -0
- package/README.md +12 -6
- package/dist/catalog/index.d.ts +3 -3
- package/dist/catalog/index.js +6 -2
- package/dist/{chunk-UNLCGDCB.js → chunk-LKQM3GSQ.js} +3 -3
- package/dist/{chunk-KWNFZ5NC.js → chunk-LURJMWYM.js} +237 -185
- package/dist/{chunk-KWNFZ5NC.js.map → chunk-LURJMWYM.js.map} +1 -1
- package/dist/{chunk-DVVQRZJ5.js → chunk-MILF5MRS.js} +11 -2
- package/dist/chunk-MILF5MRS.js.map +1 -0
- package/dist/{chunk-YLJWC4N2.js → chunk-PMVJP5I4.js} +2 -2
- package/dist/{chunk-4RGHZWQC.js → chunk-RM3I6WZH.js} +2 -2
- package/dist/{chunk-4RGHZWQC.js.map → chunk-RM3I6WZH.js.map} +1 -1
- package/dist/{chunk-FIJOMGPG.js → chunk-UP6QQVRS.js} +2 -2
- package/dist/{chunk-WC3VADZC.js → chunk-VYOMXE6I.js} +46 -4
- package/dist/chunk-VYOMXE6I.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/{controls-Syr6drzO.d.ts → controls-Ec6P0Vpy.d.ts} +9 -2
- package/dist/editor/index.d.ts +3 -3
- package/dist/editor/index.js +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/react/index.d.ts +14 -3
- package/dist/react/index.js +18 -9
- package/dist/react/index.js.map +1 -1
- package/dist/recorder-HKFFHQES.js +10 -0
- package/dist/{renderer-SNYZu__R.d.ts → renderer-Bm0oQ7Bw.d.ts} +1 -1
- package/dist/svelte/index.d.ts +16 -5
- package/dist/svelte/index.js +16 -11
- package/dist/svelte/index.js.map +1 -1
- package/dist/{types-9J6NWaN3.d.ts → types-C7g8-lTX.d.ts} +9 -3
- package/dist/vector/index.d.ts +27 -1
- package/dist/vector/index.js +4 -2
- package/package.json +1 -1
- package/dist/chunk-DVVQRZJ5.js.map +0 -1
- package/dist/chunk-WC3VADZC.js.map +0 -1
- package/dist/recorder-KEX3U35D.js +0 -10
- /package/dist/{chunk-UNLCGDCB.js.map → chunk-LKQM3GSQ.js.map} +0 -0
- /package/dist/{chunk-YLJWC4N2.js.map → chunk-PMVJP5I4.js.map} +0 -0
- /package/dist/{chunk-FIJOMGPG.js.map → chunk-UP6QQVRS.js.map} +0 -0
- /package/dist/{recorder-KEX3U35D.js.map → recorder-HKFFHQES.js.map} +0 -0
package/dist/react/index.js
CHANGED
|
@@ -2,18 +2,19 @@ import {
|
|
|
2
2
|
effectDefaults,
|
|
3
3
|
getEffect,
|
|
4
4
|
renderVectorEffect
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-LURJMWYM.js";
|
|
6
6
|
import {
|
|
7
7
|
svgFrameMarkup,
|
|
8
|
+
svgInlineLayout,
|
|
8
9
|
svgViewBox
|
|
9
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-MILF5MRS.js";
|
|
10
11
|
import {
|
|
11
12
|
SvgPlayer
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-RM3I6WZH.js";
|
|
13
14
|
import "../chunk-OHW7UG6D.js";
|
|
14
15
|
import "../chunk-EHDJ3CPJ.js";
|
|
15
16
|
import "../chunk-WMZQCUFJ.js";
|
|
16
|
-
import "../chunk-
|
|
17
|
+
import "../chunk-VYOMXE6I.js";
|
|
17
18
|
|
|
18
19
|
// src/react/index.tsx
|
|
19
20
|
import {
|
|
@@ -28,10 +29,16 @@ var CAP_HEIGHT = "0.72em";
|
|
|
28
29
|
function prefersReducedMotion() {
|
|
29
30
|
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
30
31
|
}
|
|
31
|
-
function hostStyle(svg, inline, style) {
|
|
32
|
+
function hostStyle(svg, inline, contain, style) {
|
|
32
33
|
const box = svgViewBox(svg);
|
|
33
34
|
const aspectRatio = box ? `${box[2]} / ${box[3]}` : void 0;
|
|
34
|
-
const
|
|
35
|
+
const spill = inline && contain ? svgInlineLayout(svg)?.overhang : void 0;
|
|
36
|
+
const base = inline ? {
|
|
37
|
+
display: "inline-block",
|
|
38
|
+
height: typeof inline === "string" ? inline : CAP_HEIGHT,
|
|
39
|
+
aspectRatio,
|
|
40
|
+
...spill && { margin: `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em` }
|
|
41
|
+
} : { display: "block", aspectRatio };
|
|
35
42
|
return { position: "relative", ...base, ...style };
|
|
36
43
|
}
|
|
37
44
|
var LAYER = { position: "absolute", inset: 0, display: "block" };
|
|
@@ -54,6 +61,7 @@ function SvgPlayerView(props) {
|
|
|
54
61
|
poster,
|
|
55
62
|
reducedMotion = "user",
|
|
56
63
|
inline,
|
|
64
|
+
contain,
|
|
57
65
|
style,
|
|
58
66
|
...rest
|
|
59
67
|
} = props;
|
|
@@ -65,6 +73,7 @@ function SvgPlayerView(props) {
|
|
|
65
73
|
const at = poster ?? (trigger === "hover" || trigger === "press" ? "first" : "last");
|
|
66
74
|
const markup = useMemo(() => svgFrameMarkup(motion, { at, steps }) ?? "", [motion, at, steps]);
|
|
67
75
|
const stepsKey = steps?.join(",");
|
|
76
|
+
const enter = playOnEnter ?? (loop === false && !trigger);
|
|
68
77
|
useIsomorphicLayoutEffect(() => {
|
|
69
78
|
const host = mount.current;
|
|
70
79
|
if (!host) return;
|
|
@@ -78,7 +87,7 @@ function SvgPlayerView(props) {
|
|
|
78
87
|
steps,
|
|
79
88
|
trigger,
|
|
80
89
|
pauseWhenHidden,
|
|
81
|
-
playOnEnter,
|
|
90
|
+
playOnEnter: enter,
|
|
82
91
|
hideWhenPaused,
|
|
83
92
|
clock,
|
|
84
93
|
onPick: (id) => callbacks.current.onPick?.(id)
|
|
@@ -92,11 +101,11 @@ function SvgPlayerView(props) {
|
|
|
92
101
|
if (still.current) still.current.style.display = "block";
|
|
93
102
|
callbacks.current.onPlayer?.(void 0);
|
|
94
103
|
};
|
|
95
|
-
}, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden,
|
|
104
|
+
}, [motion, loop, autoplay, hold, duration, stepsKey, trigger, pauseWhenHidden, enter, hideWhenPaused, clock, reducedMotion]);
|
|
96
105
|
useEffect(() => {
|
|
97
106
|
player.current?.setSpeed(speed ?? 1);
|
|
98
107
|
}, [speed]);
|
|
99
|
-
return /* @__PURE__ */ jsxs("span", { ...rest, style: hostStyle(motion.svg, inline, style), children: [
|
|
108
|
+
return /* @__PURE__ */ jsxs("span", { ...rest, style: hostStyle(motion.svg, inline, contain, style), children: [
|
|
100
109
|
/* @__PURE__ */ jsx("span", { ref: still, "aria-hidden": "true", style: LAYER, dangerouslySetInnerHTML: { __html: markup } }),
|
|
101
110
|
/* @__PURE__ */ jsx("span", { ref: mount, style: LAYER })
|
|
102
111
|
] });
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +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":[]}
|
|
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, svgInlineLayout, 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 /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop={false}`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\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 * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\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, contain: boolean | undefined, style?: CSSProperties): CSSProperties {\n const box = svgViewBox(svg);\n const aspectRatio = box ? `${box[2]} / ${box[3]}` : undefined;\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n const base: CSSProperties = inline\n ? {\n display: \"inline-block\",\n height: typeof inline === \"string\" ? inline : CAP_HEIGHT,\n aspectRatio,\n ...(spill && { margin: `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em` }),\n }\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 contain,\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 // A one-shot waits until it is seen.\n const enter = playOnEnter ?? (loop === false && !trigger);\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: enter,\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, enter, 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, contain, 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":";;;;;;;;;;;;;;;;;;;AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAmLH,SACE,KADF;AAzKJ,IAAM,4BAA4B,OAAO,WAAW,cAAc,YAAY;AAG9E,IAAM,aAAa;AAkEnB,SAAS,uBAAgC;AACvC,SAAO,OAAO,eAAe,cAAc,WAAW,kCAAkC,EAAE;AAC5F;AAGA,SAAS,UAAU,KAAa,QAAsC,SAA8B,OAAsC;AACxI,QAAM,MAAM,WAAW,GAAG;AAC1B,QAAM,cAAc,MAAM,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK;AACpD,QAAM,QAAQ,UAAU,UAAU,gBAAgB,GAAG,GAAG,WAAW;AACnE,QAAM,OAAsB,SACxB;AAAA,IACE,SAAS;AAAA,IACT,QAAQ,OAAO,WAAW,WAAW,SAAS;AAAA,IAC9C;AAAA,IACA,GAAI,SAAS,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,MAAM,KAAK,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,KAAK;AAAA,EAC7F,IACA,EAAE,SAAS,SAAS,YAAY;AACpC,SAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GAAG,MAAM;AACnD;AAEA,IAAM,QAAuB,EAAE,UAAU,YAAY,OAAO,GAAG,SAAS,QAAQ;AAOzE,SAAS,cAAc,OAA2B;AACvD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,QAAQ,OAAwB,IAAI;AAC1C,QAAM,QAAQ,OAAwB,IAAI;AAC1C,QAAM,SAAS,OAA8B,MAAS;AAEtD,QAAM,YAAY,OAAO,EAAE,QAAQ,SAAS,CAAC;AAC7C,YAAU,UAAU,EAAE,QAAQ,SAAS;AAEvC,QAAM,KAAK,WAAW,YAAY,WAAW,YAAY,UAAU,UAAU;AAC7E,QAAM,SAAS,QAAQ,MAAM,eAAe,QAAQ,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAC7F,QAAM,WAAW,OAAO,KAAK,GAAG;AAEhC,QAAM,QAAQ,gBAAgB,SAAS,SAAS,CAAC;AAEjD,4BAA0B,MAAM;AAC9B,UAAM,OAAO,MAAM;AACnB,QAAI,CAAC,KAAM;AACX,QAAI,kBAAkB,YAAa,kBAAkB,UAAU,qBAAqB,EAAI;AACxF,UAAM,IAAI,IAAI,UAAU,MAAM,QAAQ;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA,QAAQ,CAAC,OAAO,UAAU,QAAQ,SAAS,EAAE;AAAA,IAC/C,CAAC;AACD,WAAO,UAAU;AAEjB,QAAI,MAAM,QAAS,OAAM,QAAQ,MAAM,UAAU;AACjD,cAAU,QAAQ,WAAW,CAAC;AAC9B,WAAO,MAAM;AACX,QAAE,QAAQ;AACV,aAAO,UAAU;AACjB,UAAI,MAAM,QAAS,OAAM,QAAQ,MAAM,UAAU;AACjD,gBAAU,QAAQ,WAAW,MAAS;AAAA,IACxC;AAAA,EAGF,GAAG,CAAC,QAAQ,MAAM,UAAU,MAAM,UAAU,UAAU,SAAS,iBAAiB,OAAO,gBAAgB,OAAO,aAAa,CAAC;AAE5H,YAAU,MAAM;AACd,WAAO,SAAS,SAAS,SAAS,CAAC;AAAA,EACrC,GAAG,CAAC,KAAK,CAAC;AAEV,SACE,qBAAC,UAAM,GAAG,MAAM,OAAO,UAAU,OAAO,KAAK,QAAQ,SAAS,KAAK,GACjE;AAAA,wBAAC,UAAK,KAAK,OAAO,eAAY,QAAO,OAAO,OAAO,yBAAyB,EAAE,QAAQ,OAAO,GAAG;AAAA,IAChG,oBAAC,UAAK,KAAK,OAAO,OAAO,OAAO;AAAA,KAClC;AAEJ;AAmBO,SAAS,aAAa,OAA0B;AACrD,QAAM,EAAE,IAAI,QAAQ,QAAQ,WAAW,GAAG,KAAK,IAAI;AACnD,QAAM,SAAS,OAAO,OAAO,WAAW,UAAU,EAAE,IAAI;AACxD,MAAI,CAAC,UAAU,OAAO,SAAS;AAC7B,UAAM,IAAI,MAAM,IAAI,OAAO,OAAO,WAAW,KAAK,QAAQ,EAAE,yEAAyE;AACvI,QAAM,YAAY,KAAK,UAAU,UAAU,CAAC,CAAC;AAC7C,QAAM,SAAS;AAAA,IACb,MAAM,mBAAmB,QAAQ,eAAe,QAAQ,EAAE,QAAQ,OAAO,CAAC,CAAC;AAAA;AAAA;AAAA,IAG3E,CAAC,QAAQ,QAAQ,SAAS;AAAA,EAC5B;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MACA,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,QAAQ,QAAQ,WAAW;AAAA,MAC1B,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TextureSettings, a as ControlDef, C as ControlValue } from './controls-
|
|
1
|
+
import { T as TextureSettings, a as ControlDef, C as ControlValue } from './controls-Ec6P0Vpy.js';
|
|
2
2
|
|
|
3
3
|
/** Why an image control's URL could not be shown. */
|
|
4
4
|
declare class ImageLoadError extends Error {
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ActionReturn } from 'svelte/action';
|
|
2
|
-
import { V as VectorEffect } from '../types-
|
|
3
|
-
import { C as ControlValue } from '../controls-
|
|
2
|
+
import { V as VectorEffect } from '../types-C7g8-lTX.js';
|
|
3
|
+
import { C as ControlValue } from '../controls-Ec6P0Vpy.js';
|
|
4
4
|
import { a as SvgTrigger, C as Clock, b as SvgPlayer, S as SvgMotion } from '../svg-XugR-6Ec.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -49,7 +49,11 @@ interface SvgPlayerParams {
|
|
|
49
49
|
trigger?: SvgTrigger;
|
|
50
50
|
/** Pause while scrolled out of view. */
|
|
51
51
|
pauseWhenHidden?: boolean;
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Wait to start until scrolled into view. Default `true` when it plays once
|
|
54
|
+
* (`loop: false`, no trigger), so a one-shot further down the page does not
|
|
55
|
+
* finish before anyone sees it; `false` plays at mount.
|
|
56
|
+
*/
|
|
53
57
|
playOnEnter?: boolean;
|
|
54
58
|
/** Hide the picture while paused or reset. */
|
|
55
59
|
hideWhenPaused?: boolean;
|
|
@@ -78,6 +82,13 @@ interface SvgPlayerParams {
|
|
|
78
82
|
* viewBox's aspect ratio.
|
|
79
83
|
*/
|
|
80
84
|
inline?: boolean | string;
|
|
85
|
+
/**
|
|
86
|
+
* With `inline`, keep room for what spills past the word (descenders,
|
|
87
|
+
* outlines, glows, drips): a margin of the picture's `data-overhang` (see
|
|
88
|
+
* `svgInlineLayout`), so the lines around it make space instead of being
|
|
89
|
+
* painted over. Default `false`.
|
|
90
|
+
*/
|
|
91
|
+
contain?: boolean;
|
|
81
92
|
}
|
|
82
93
|
/** A catalog vector effect by id (or the effect itself), with its values. */
|
|
83
94
|
interface VectorEffectByIdParams extends SvgPlayerParams {
|
|
@@ -123,8 +134,8 @@ declare function vectorEffectStill(params: VectorEffectParams): VectorEffectStil
|
|
|
123
134
|
* destroy. A still from {@link vectorEffectStill} in the element is hidden
|
|
124
135
|
* while the player runs and shown again after; without one, the action makes
|
|
125
136
|
* its own (so reduced motion still shows the picture) and sizes the element
|
|
126
|
-
* from the viewBox, leaving alone any of `position`, `display`, `height
|
|
127
|
-
* `aspect-ratio` the element's style already sets.
|
|
137
|
+
* from the viewBox, leaving alone any of `position`, `display`, `height`,
|
|
138
|
+
* `aspect-ratio` and `margin` the element's style already sets.
|
|
128
139
|
*
|
|
129
140
|
* For an attachment, `{@attach fromAction(vectorEffect, () => params)}`
|
|
130
141
|
* (`fromAction` is from `svelte/attachments`). Throws when `id` is not a
|
package/dist/svelte/index.js
CHANGED
|
@@ -2,25 +2,26 @@ import {
|
|
|
2
2
|
effectDefaults,
|
|
3
3
|
getEffect,
|
|
4
4
|
renderVectorEffect
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-LURJMWYM.js";
|
|
6
6
|
import {
|
|
7
7
|
svgFrameMarkup,
|
|
8
|
+
svgInlineLayout,
|
|
8
9
|
svgViewBox
|
|
9
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-MILF5MRS.js";
|
|
10
11
|
import {
|
|
11
12
|
SvgPlayer
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-RM3I6WZH.js";
|
|
13
14
|
import "../chunk-OHW7UG6D.js";
|
|
14
15
|
import "../chunk-EHDJ3CPJ.js";
|
|
15
16
|
import "../chunk-WMZQCUFJ.js";
|
|
16
|
-
import "../chunk-
|
|
17
|
+
import "../chunk-VYOMXE6I.js";
|
|
17
18
|
|
|
18
19
|
// src/svelte/index.ts
|
|
19
20
|
var CAP_HEIGHT = "0.72em";
|
|
20
21
|
var STILL_ATTR = "data-flair-still";
|
|
21
22
|
var LAYER = "position:absolute;inset:0;display:block";
|
|
22
23
|
function resolve(params, cache) {
|
|
23
|
-
if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline };
|
|
24
|
+
if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline, contain: params.contain };
|
|
24
25
|
const { id, values, preset } = params;
|
|
25
26
|
const effect = typeof id === "string" ? getEffect(id) : id;
|
|
26
27
|
if (!effect || effect.kind !== "vector")
|
|
@@ -38,10 +39,11 @@ function resolve(params, cache) {
|
|
|
38
39
|
motion,
|
|
39
40
|
hold: params.hold ?? effect.hold,
|
|
40
41
|
trigger: params.trigger ?? effect.trigger,
|
|
41
|
-
inline: params.inline ?? values?.inline === true
|
|
42
|
+
inline: params.inline ?? values?.inline === true,
|
|
43
|
+
contain: params.contain
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
|
-
function hostStyle(svg, inline) {
|
|
46
|
+
function hostStyle(svg, inline, contain) {
|
|
45
47
|
const box = svgViewBox(svg);
|
|
46
48
|
const style = [
|
|
47
49
|
["position", "relative"],
|
|
@@ -49,8 +51,11 @@ function hostStyle(svg, inline) {
|
|
|
49
51
|
];
|
|
50
52
|
if (inline) style.push(["height", typeof inline === "string" ? inline : CAP_HEIGHT]);
|
|
51
53
|
if (box) style.push(["aspect-ratio", `${box[2]} / ${box[3]}`]);
|
|
54
|
+
const spill = inline && contain ? svgInlineLayout(svg)?.overhang : void 0;
|
|
55
|
+
if (spill) style.push(["margin", `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em`]);
|
|
52
56
|
return style;
|
|
53
57
|
}
|
|
58
|
+
var enters = (r, p) => p.playOnEnter ?? (p.loop === false && !r.trigger);
|
|
54
59
|
function stillMarkup(r, params) {
|
|
55
60
|
const at = params.poster ?? (r.trigger === "hover" || r.trigger === "press" ? "first" : "last");
|
|
56
61
|
return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? "";
|
|
@@ -59,7 +64,7 @@ function vectorEffectStill(params) {
|
|
|
59
64
|
const r = resolve(params);
|
|
60
65
|
return {
|
|
61
66
|
markup: `<span ${STILL_ATTR} aria-hidden="true" style="${LAYER}">${stillMarkup(r, params)}</span>`,
|
|
62
|
-
style: hostStyle(r.motion.svg, r.inline).map(([k, v]) => `${k}: ${v}`).join("; ")
|
|
67
|
+
style: hostStyle(r.motion.svg, r.inline, r.contain).map(([k, v]) => `${k}: ${v}`).join("; ")
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
function prefersReducedMotion() {
|
|
@@ -75,7 +80,7 @@ function playerKey(r, p) {
|
|
|
75
80
|
p.steps?.join(","),
|
|
76
81
|
r.trigger,
|
|
77
82
|
p.pauseWhenHidden,
|
|
78
|
-
p
|
|
83
|
+
enters(r, p),
|
|
79
84
|
p.hideWhenPaused,
|
|
80
85
|
p.clock,
|
|
81
86
|
p.reducedMotion ?? "user"
|
|
@@ -104,7 +109,7 @@ function vectorEffect(node, initial) {
|
|
|
104
109
|
const observer = typeof MutationObserver === "function" ? new MutationObserver(reattach) : void 0;
|
|
105
110
|
observer?.observe(node, { childList: true });
|
|
106
111
|
const size = () => {
|
|
107
|
-
const style = hostStyle(resolved.motion.svg, resolved.inline);
|
|
112
|
+
const style = hostStyle(resolved.motion.svg, resolved.inline, resolved.contain);
|
|
108
113
|
for (const [prop, value] of style) {
|
|
109
114
|
if (!sized.has(prop) && node.style.getPropertyValue(prop)) continue;
|
|
110
115
|
node.style.setProperty(prop, value);
|
|
@@ -143,7 +148,7 @@ function vectorEffect(node, initial) {
|
|
|
143
148
|
steps: params.steps,
|
|
144
149
|
trigger: resolved.trigger,
|
|
145
150
|
pauseWhenHidden: params.pauseWhenHidden,
|
|
146
|
-
playOnEnter: params
|
|
151
|
+
playOnEnter: enters(resolved, params),
|
|
147
152
|
hideWhenPaused: params.hideWhenPaused,
|
|
148
153
|
clock: params.clock,
|
|
149
154
|
// The latest params' callback, without remaking the player.
|
package/dist/svelte/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/svelte/index.ts"],"sourcesContent":["/**\n * Svelte 5 bindings for Flair's vector effects, written as plain TypeScript so\n * they need no Svelte compiler: the `vectorEffect` action plays a catalog\n * effect (or any {@link SvgMotion}) in an element, and `vectorEffectStill`\n * gives the markup and style of its still for server rendering.\n *\n * ```svelte\n * <script>\n * import { vectorEffect, vectorEffectStill } from \"@pixerate/flair/svelte\";\n * const params = { id: \"drip-text\", values: { word: \"Slop\" }, loop: false };\n * const still = vectorEffectStill(params);\n * </script>\n *\n * <span use:vectorEffect={params} style={still.style}>{@html still.markup}</span>\n * ```\n *\n * The still is in place from the first paint; the live `SvgPlayer` takes over\n * after mount and is disposed on destroy. Under `prefers-reduced-motion:\n * reduce` the still stays.\n *\n * Needs Svelte 5 (an optional peer dependency of the package); only its types\n * are imported.\n *\n * @packageDocumentation\n */\n\nimport type { ActionReturn } from \"svelte/action\";\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/** 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/** Marks the still's layer, so the action can find it (and hide it while the player runs). */\nconst STILL_ATTR = \"data-flair-still\";\n\nconst LAYER = \"position:absolute;inset:0;display:block\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\n/** Options shared by an effect by id and a motion of your own. */\nexport interface SvgPlayerParams {\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); changed without remaking the player. */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600, or the effect's). */\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`; default the effect's). */\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 and instead of it under reduced\n * motion: `\"last\"` (default; the rest frame, `\"first\"` for a hover or press\n * 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). Default: the effect's `inline`\n * control. Otherwise it is a block as wide as its container with the\n * viewBox's aspect ratio.\n */\n inline?: boolean | string;\n}\n\n/** A catalog vector effect by id (or the effect itself), with its values. */\nexport interface VectorEffectByIdParams extends SvgPlayerParams {\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. Compared by content. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n motion?: never;\n}\n\n/** Any {@link SvgMotion}; a new object restarts the player. */\nexport interface SvgMotionParams extends SvgPlayerParams {\n /** The picture and its animations. */\n motion: SvgMotion;\n id?: never;\n values?: never;\n preset?: never;\n}\n\n/** What `vectorEffect` and `vectorEffectStill` take: an effect by `id`, or a `motion`. */\nexport type VectorEffectParams = VectorEffectByIdParams | SvgMotionParams;\n\n/** A still of the picture, for `{@html markup}` inside an element with `style`. */\nexport interface VectorEffectStill {\n /** The still's layer: safe `<svg>` markup (see `svgFrameMarkup`) in an absolutely placed `<span>`. */\n markup: string;\n /** The host's inline style: its position and size from the viewBox. */\n style: string;\n}\n\ninterface Resolved {\n motion: SvgMotion;\n hold?: number;\n trigger?: SvgTrigger;\n inline?: boolean | string;\n}\n\n/** Remembers the last motion made from an effect, so values compared by content do not remake it. */\ntype MotionCache = { key: string; effect: VectorEffectDefinition; motion: SvgMotion };\n\nfunction resolve(params: VectorEffectParams, cache?: { current?: MotionCache }): Resolved {\n if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline };\n const { id, values, preset } = params as VectorEffectByIdParams;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(\n `\"${typeof id === \"string\" ? id : (effect?.id ?? id)}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" }), or pass a motion`,\n );\n const key = JSON.stringify([preset ?? null, values ?? {}]);\n let motion: SvgMotion;\n if (cache?.current && cache.current.effect === effect && cache.current.key === key) motion = cache.current.motion;\n else {\n motion = renderVectorEffect(effect, effectDefaults(effect, { preset, values }));\n if (cache) cache.current = { key, effect, motion };\n }\n return {\n motion,\n hold: params.hold ?? effect.hold,\n trigger: params.trigger ?? effect.trigger,\n inline: params.inline ?? values?.inline === true,\n };\n}\n\n/** The host's sizing from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined): [string, string][] {\n const box = svgViewBox(svg);\n const style: [string, string][] = [\n [\"position\", \"relative\"],\n [\"display\", inline ? \"inline-block\" : \"block\"],\n ];\n if (inline) style.push([\"height\", typeof inline === \"string\" ? inline : CAP_HEIGHT]);\n if (box) style.push([\"aspect-ratio\", `${box[2]} / ${box[3]}`]);\n return style;\n}\n\nfunction stillMarkup(r: Resolved, params: SvgPlayerParams): string {\n const at = params.poster ?? (r.trigger === \"hover\" || r.trigger === \"press\" ? \"first\" : \"last\");\n return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? \"\";\n}\n\n/**\n * The still of an effect or motion, for server rendering: put `markup` in the\n * element that has the `vectorEffect` action, with `style` as its style, so\n * the page shows the picture before the player starts (and instead of it\n * under reduced motion), in the room the player will take.\n * Throws when `id` is not a vector effect.\n */\nexport function vectorEffectStill(params: VectorEffectParams): VectorEffectStill {\n const r = resolve(params);\n return {\n markup: `<span ${STILL_ATTR} aria-hidden=\"true\" style=\"${LAYER}\">${stillMarkup(r, params)}</span>`,\n style: hostStyle(r.motion.svg, r.inline)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \"),\n };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The options whose change remakes the player (`speed` is set on it instead). */\nfunction playerKey(r: Resolved, p: SvgPlayerParams): unknown[] {\n return [\n r.motion,\n p.loop,\n p.autoplay,\n r.hold,\n p.duration,\n p.steps?.join(\",\"),\n r.trigger,\n p.pauseWhenHidden,\n p.playOnEnter,\n p.hideWhenPaused,\n p.clock,\n p.reducedMotion ?? \"user\",\n ];\n}\n\n/**\n * A Svelte action that plays a vector effect in its element:\n * `<span use:vectorEffect={{ id: \"drip-text\", values: { word: \"Slop\" }, loop: false }} />`.\n *\n * It makes an `SvgPlayer` in a layer of its own, remakes it when the params\n * change (values by content; `speed` without remaking) and disposes it on\n * destroy. A still from {@link vectorEffectStill} in the element is hidden\n * while the player runs and shown again after; without one, the action makes\n * its own (so reduced motion still shows the picture) and sizes the element\n * from the viewBox, leaving alone any of `position`, `display`, `height` and\n * `aspect-ratio` the element's style already sets.\n *\n * For an attachment, `{@attach fromAction(vectorEffect, () => params)}`\n * (`fromAction` is from `svelte/attachments`). Throws when `id` is not a\n * vector effect.\n */\nexport function vectorEffect(node: HTMLElement, initial: VectorEffectParams): ActionReturn<VectorEffectParams> {\n const cache: { current?: MotionCache } = {};\n const doc = node.ownerDocument;\n const mount = doc.createElement(\"span\");\n mount.style.cssText = LAYER;\n node.appendChild(mount);\n\n let params = initial;\n let resolved = resolve(params, cache);\n let player: SvgPlayer | undefined;\n let key: unknown[] = [];\n /** The still the action made itself, when the element had none. */\n let ownStill: HTMLElement | undefined;\n /** Sizing properties the action set (the element's own style had them empty). */\n const sized = new Set<string>();\n\n const stills = () => node.querySelectorAll<HTMLElement>(`:scope > [${STILL_ATTR}]`);\n const showStills = (show: boolean) => {\n for (const s of stills()) s.style.display = show ? \"block\" : \"none\";\n };\n // When the params change Svelte replaces a server still (`{@html}`), and when\n // it is the element's only child, by setting `innerHTML`, which takes the\n // player's layer out too: put the layer back, and keep a new still hidden.\n const reattach = () => {\n if (mount.parentNode !== node) node.appendChild(mount);\n if (player) showStills(false);\n };\n const observer = typeof MutationObserver === \"function\" ? new MutationObserver(reattach) : undefined;\n observer?.observe(node, { childList: true });\n\n const size = () => {\n const style = hostStyle(resolved.motion.svg, resolved.inline);\n for (const [prop, value] of style) {\n if (!sized.has(prop) && node.style.getPropertyValue(prop)) continue;\n node.style.setProperty(prop, value);\n sized.add(prop);\n }\n // A property the new sizing drops (`height` when no longer inline) is cleared.\n for (const prop of sized)\n if (!style.some(([p]) => p === prop)) {\n node.style.removeProperty(prop);\n sized.delete(prop);\n }\n };\n\n if (stills().length === 0) {\n ownStill = doc.createElement(\"span\");\n ownStill.setAttribute(STILL_ATTR, \"\");\n ownStill.setAttribute(\"aria-hidden\", \"true\");\n ownStill.style.cssText = LAYER;\n node.insertBefore(ownStill, mount);\n size();\n }\n\n const stop = () => {\n if (!player) return;\n player.dispose();\n player = undefined;\n showStills(true);\n params.onPlayer?.(undefined);\n };\n\n const start = () => {\n const reduced = params.reducedMotion ?? \"user\";\n if (reduced === \"always\" || (reduced === \"user\" && prefersReducedMotion())) return;\n player = new SvgPlayer(mount, resolved.motion, {\n loop: params.loop,\n autoplay: params.autoplay,\n speed: params.speed,\n hold: resolved.hold,\n duration: params.duration,\n steps: params.steps,\n trigger: resolved.trigger,\n pauseWhenHidden: params.pauseWhenHidden,\n playOnEnter: params.playOnEnter,\n hideWhenPaused: params.hideWhenPaused,\n clock: params.clock,\n // The latest params' callback, without remaking the player.\n onPick: (id) => params.onPick?.(id),\n });\n showStills(false);\n params.onPlayer?.(player);\n };\n\n const apply = () => {\n reattach();\n if (ownStill) {\n ownStill.innerHTML = stillMarkup(resolved, params);\n size();\n }\n const next = playerKey(resolved, params);\n if (player && next.every((v, i) => v === key[i])) {\n player.setSpeed(params.speed ?? 1);\n return;\n }\n key = next;\n stop();\n start();\n };\n\n apply();\n\n return {\n update(next) {\n params = next;\n resolved = resolve(params, cache);\n apply();\n },\n destroy() {\n observer?.disconnect();\n stop();\n mount.remove();\n ownStill?.remove();\n for (const prop of sized) node.style.removeProperty(prop);\n },\n };\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/svelte/index.ts"],"sourcesContent":["/**\n * Svelte 5 bindings for Flair's vector effects, written as plain TypeScript so\n * they need no Svelte compiler: the `vectorEffect` action plays a catalog\n * effect (or any {@link SvgMotion}) in an element, and `vectorEffectStill`\n * gives the markup and style of its still for server rendering.\n *\n * ```svelte\n * <script>\n * import { vectorEffect, vectorEffectStill } from \"@pixerate/flair/svelte\";\n * const params = { id: \"drip-text\", values: { word: \"Slop\" }, loop: false };\n * const still = vectorEffectStill(params);\n * </script>\n *\n * <span use:vectorEffect={params} style={still.style}>{@html still.markup}</span>\n * ```\n *\n * The still is in place from the first paint; the live `SvgPlayer` takes over\n * after mount and is disposed on destroy. Under `prefers-reduced-motion:\n * reduce` the still stays.\n *\n * Needs Svelte 5 (an optional peer dependency of the package); only its types\n * are imported.\n *\n * @packageDocumentation\n */\n\nimport type { ActionReturn } from \"svelte/action\";\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, svgInlineLayout, svgViewBox } from \"../vector/static.js\";\nimport { SvgPlayer, type SvgTrigger } from \"../vector/svg.js\";\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/** Marks the still's layer, so the action can find it (and hide it while the player runs). */\nconst STILL_ATTR = \"data-flair-still\";\n\nconst LAYER = \"position:absolute;inset:0;display:block\";\n\n/** When to leave the still in place of the animation. */\nexport type ReducedMotion = \"user\" | \"always\" | \"never\";\n\n/** Options shared by an effect by id and a motion of your own. */\nexport interface SvgPlayerParams {\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); changed without remaking the player. */\n speed?: number;\n /** Ms to rest on the last frame before looping (default 600, or the effect's). */\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`; default the effect's). */\n trigger?: SvgTrigger;\n /** Pause while scrolled out of view. */\n pauseWhenHidden?: boolean;\n /**\n * Wait to start until scrolled into view. Default `true` when it plays once\n * (`loop: false`, no trigger), so a one-shot further down the page does not\n * finish before anyone sees it; `false` plays at mount.\n */\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 and instead of it under reduced\n * motion: `\"last\"` (default; the rest frame, `\"first\"` for a hover or press\n * 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). Default: the effect's `inline`\n * control. Otherwise it is a block as wide as its container with the\n * viewBox's aspect ratio.\n */\n inline?: boolean | string;\n /**\n * With `inline`, keep room for what spills past the word (descenders,\n * outlines, glows, drips): a margin of the picture's `data-overhang` (see\n * `svgInlineLayout`), so the lines around it make space instead of being\n * painted over. Default `false`.\n */\n contain?: boolean;\n}\n\n/** A catalog vector effect by id (or the effect itself), with its values. */\nexport interface VectorEffectByIdParams extends SvgPlayerParams {\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. Compared by content. */\n values?: Record<string, ControlValue>;\n /** A preset's name to start from. */\n preset?: string;\n motion?: never;\n}\n\n/** Any {@link SvgMotion}; a new object restarts the player. */\nexport interface SvgMotionParams extends SvgPlayerParams {\n /** The picture and its animations. */\n motion: SvgMotion;\n id?: never;\n values?: never;\n preset?: never;\n}\n\n/** What `vectorEffect` and `vectorEffectStill` take: an effect by `id`, or a `motion`. */\nexport type VectorEffectParams = VectorEffectByIdParams | SvgMotionParams;\n\n/** A still of the picture, for `{@html markup}` inside an element with `style`. */\nexport interface VectorEffectStill {\n /** The still's layer: safe `<svg>` markup (see `svgFrameMarkup`) in an absolutely placed `<span>`. */\n markup: string;\n /** The host's inline style: its position and size from the viewBox. */\n style: string;\n}\n\ninterface Resolved {\n motion: SvgMotion;\n hold?: number;\n trigger?: SvgTrigger;\n inline?: boolean | string;\n contain?: boolean;\n}\n\n/** Remembers the last motion made from an effect, so values compared by content do not remake it. */\ntype MotionCache = { key: string; effect: VectorEffectDefinition; motion: SvgMotion };\n\nfunction resolve(params: VectorEffectParams, cache?: { current?: MotionCache }): Resolved {\n if (params.motion) return { motion: params.motion, hold: params.hold, trigger: params.trigger, inline: params.inline, contain: params.contain };\n const { id, values, preset } = params as VectorEffectByIdParams;\n const effect = typeof id === \"string\" ? getEffect(id) : id;\n if (!effect || effect.kind !== \"vector\")\n throw new Error(\n `\"${typeof id === \"string\" ? id : (effect?.id ?? id)}\" is not a vector effect; pick one from findEffects({ kind: \"vector\" }), or pass a motion`,\n );\n const key = JSON.stringify([preset ?? null, values ?? {}]);\n let motion: SvgMotion;\n if (cache?.current && cache.current.effect === effect && cache.current.key === key) motion = cache.current.motion;\n else {\n motion = renderVectorEffect(effect, effectDefaults(effect, { preset, values }));\n if (cache) cache.current = { key, effect, motion };\n }\n return {\n motion,\n hold: params.hold ?? effect.hold,\n trigger: params.trigger ?? effect.trigger,\n inline: params.inline ?? values?.inline === true,\n contain: params.contain,\n };\n}\n\n/** The host's sizing from the picture's viewBox, so the still and the player take the same room. */\nfunction hostStyle(svg: string, inline: boolean | string | undefined, contain?: boolean): [string, string][] {\n const box = svgViewBox(svg);\n const style: [string, string][] = [\n [\"position\", \"relative\"],\n [\"display\", inline ? \"inline-block\" : \"block\"],\n ];\n if (inline) style.push([\"height\", typeof inline === \"string\" ? inline : CAP_HEIGHT]);\n if (box) style.push([\"aspect-ratio\", `${box[2]} / ${box[3]}`]);\n const spill = inline && contain ? svgInlineLayout(svg)?.overhang : undefined;\n if (spill) style.push([\"margin\", `${spill.top}em ${spill.right}em ${spill.bottom}em ${spill.left}em`]);\n return style;\n}\n\n/** Whether to wait until scrolled into view: as asked, else when it plays once. */\nconst enters = (r: Resolved, p: SvgPlayerParams) => p.playOnEnter ?? (p.loop === false && !r.trigger);\n\nfunction stillMarkup(r: Resolved, params: SvgPlayerParams): string {\n const at = params.poster ?? (r.trigger === \"hover\" || r.trigger === \"press\" ? \"first\" : \"last\");\n return svgFrameMarkup(r.motion, { at, steps: params.steps }) ?? \"\";\n}\n\n/**\n * The still of an effect or motion, for server rendering: put `markup` in the\n * element that has the `vectorEffect` action, with `style` as its style, so\n * the page shows the picture before the player starts (and instead of it\n * under reduced motion), in the room the player will take.\n * Throws when `id` is not a vector effect.\n */\nexport function vectorEffectStill(params: VectorEffectParams): VectorEffectStill {\n const r = resolve(params);\n return {\n markup: `<span ${STILL_ATTR} aria-hidden=\"true\" style=\"${LAYER}\">${stillMarkup(r, params)}</span>`,\n style: hostStyle(r.motion.svg, r.inline, r.contain)\n .map(([k, v]) => `${k}: ${v}`)\n .join(\"; \"),\n };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === \"function\" && matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\n/** The options whose change remakes the player (`speed` is set on it instead). */\nfunction playerKey(r: Resolved, p: SvgPlayerParams): unknown[] {\n return [\n r.motion,\n p.loop,\n p.autoplay,\n r.hold,\n p.duration,\n p.steps?.join(\",\"),\n r.trigger,\n p.pauseWhenHidden,\n enters(r, p),\n p.hideWhenPaused,\n p.clock,\n p.reducedMotion ?? \"user\",\n ];\n}\n\n/**\n * A Svelte action that plays a vector effect in its element:\n * `<span use:vectorEffect={{ id: \"drip-text\", values: { word: \"Slop\" }, loop: false }} />`.\n *\n * It makes an `SvgPlayer` in a layer of its own, remakes it when the params\n * change (values by content; `speed` without remaking) and disposes it on\n * destroy. A still from {@link vectorEffectStill} in the element is hidden\n * while the player runs and shown again after; without one, the action makes\n * its own (so reduced motion still shows the picture) and sizes the element\n * from the viewBox, leaving alone any of `position`, `display`, `height`,\n * `aspect-ratio` and `margin` the element's style already sets.\n *\n * For an attachment, `{@attach fromAction(vectorEffect, () => params)}`\n * (`fromAction` is from `svelte/attachments`). Throws when `id` is not a\n * vector effect.\n */\nexport function vectorEffect(node: HTMLElement, initial: VectorEffectParams): ActionReturn<VectorEffectParams> {\n const cache: { current?: MotionCache } = {};\n const doc = node.ownerDocument;\n const mount = doc.createElement(\"span\");\n mount.style.cssText = LAYER;\n node.appendChild(mount);\n\n let params = initial;\n let resolved = resolve(params, cache);\n let player: SvgPlayer | undefined;\n let key: unknown[] = [];\n /** The still the action made itself, when the element had none. */\n let ownStill: HTMLElement | undefined;\n /** Sizing properties the action set (the element's own style had them empty). */\n const sized = new Set<string>();\n\n const stills = () => node.querySelectorAll<HTMLElement>(`:scope > [${STILL_ATTR}]`);\n const showStills = (show: boolean) => {\n for (const s of stills()) s.style.display = show ? \"block\" : \"none\";\n };\n // When the params change Svelte replaces a server still (`{@html}`), and when\n // it is the element's only child, by setting `innerHTML`, which takes the\n // player's layer out too: put the layer back, and keep a new still hidden.\n const reattach = () => {\n if (mount.parentNode !== node) node.appendChild(mount);\n if (player) showStills(false);\n };\n const observer = typeof MutationObserver === \"function\" ? new MutationObserver(reattach) : undefined;\n observer?.observe(node, { childList: true });\n\n const size = () => {\n const style = hostStyle(resolved.motion.svg, resolved.inline, resolved.contain);\n for (const [prop, value] of style) {\n if (!sized.has(prop) && node.style.getPropertyValue(prop)) continue;\n node.style.setProperty(prop, value);\n sized.add(prop);\n }\n // A property the new sizing drops (`height` when no longer inline) is cleared.\n for (const prop of sized)\n if (!style.some(([p]) => p === prop)) {\n node.style.removeProperty(prop);\n sized.delete(prop);\n }\n };\n\n if (stills().length === 0) {\n ownStill = doc.createElement(\"span\");\n ownStill.setAttribute(STILL_ATTR, \"\");\n ownStill.setAttribute(\"aria-hidden\", \"true\");\n ownStill.style.cssText = LAYER;\n node.insertBefore(ownStill, mount);\n size();\n }\n\n const stop = () => {\n if (!player) return;\n player.dispose();\n player = undefined;\n showStills(true);\n params.onPlayer?.(undefined);\n };\n\n const start = () => {\n const reduced = params.reducedMotion ?? \"user\";\n if (reduced === \"always\" || (reduced === \"user\" && prefersReducedMotion())) return;\n player = new SvgPlayer(mount, resolved.motion, {\n loop: params.loop,\n autoplay: params.autoplay,\n speed: params.speed,\n hold: resolved.hold,\n duration: params.duration,\n steps: params.steps,\n trigger: resolved.trigger,\n pauseWhenHidden: params.pauseWhenHidden,\n playOnEnter: enters(resolved, params),\n hideWhenPaused: params.hideWhenPaused,\n clock: params.clock,\n // The latest params' callback, without remaking the player.\n onPick: (id) => params.onPick?.(id),\n });\n showStills(false);\n params.onPlayer?.(player);\n };\n\n const apply = () => {\n reattach();\n if (ownStill) {\n ownStill.innerHTML = stillMarkup(resolved, params);\n size();\n }\n const next = playerKey(resolved, params);\n if (player && next.every((v, i) => v === key[i])) {\n player.setSpeed(params.speed ?? 1);\n return;\n }\n key = next;\n stop();\n start();\n };\n\n apply();\n\n return {\n update(next) {\n params = next;\n resolved = resolve(params, cache);\n apply();\n },\n destroy() {\n observer?.disconnect();\n stop();\n mount.remove();\n ownStill?.remove();\n for (const prop of sized) node.style.removeProperty(prop);\n },\n };\n}\n\nexport type { SvgPlayer, SvgMotion, SvgTrigger };\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ControlValue, a as ControlDef } from './controls-
|
|
1
|
+
import { C as ControlValue, a as ControlDef } from './controls-Ec6P0Vpy.js';
|
|
2
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. */
|
|
@@ -96,12 +96,18 @@ declare function vectorEffectControls(effect: Pick<VectorEffect, "motion" | "con
|
|
|
96
96
|
declare function vectorControlIssues(motion: SvgMotion): string[];
|
|
97
97
|
/** A vector control as a full `ControlDef`, so editors treat both kinds of effect alike. */
|
|
98
98
|
declare function vectorControlDef(control: VectorControl, line?: number): ControlDef;
|
|
99
|
+
/** What a vector effect's colour control takes to paint in the surrounding text's colour (CSS `color`). */
|
|
100
|
+
declare const CURRENT_COLOR = "currentColor";
|
|
101
|
+
/** Whether a colour value is `currentColor` (in any case), which vector effects keep as it is. */
|
|
102
|
+
declare function isCurrentColor(value: unknown): value is string;
|
|
99
103
|
/**
|
|
100
104
|
* A vector effect's motion with its controls filled in. A string that is
|
|
101
105
|
* only a placeholder (an animation's `"{{width}}"`) becomes the value
|
|
102
106
|
* itself, so numbers stay numbers; elsewhere a placeholder becomes its text,
|
|
103
107
|
* escaped in the markup so any text is safe to show. Comments are dropped
|
|
104
|
-
* from the markup. Colours also take
|
|
108
|
+
* from the markup. Colours also take CSS colours (see `parseColor`) and
|
|
109
|
+
* `currentColor`, which paints in the surrounding text's colour (`derived`
|
|
110
|
+
* then sees the string "currentColor"); missing values, and values a
|
|
105
111
|
* control cannot take, use defaults. Placeholders no control or derived
|
|
106
112
|
* value fills stay put.
|
|
107
113
|
*/
|
|
@@ -111,4 +117,4 @@ declare function colorControl(name: string, hex: string, label?: string): Vector
|
|
|
111
117
|
/** A number control for vector effects. */
|
|
112
118
|
declare function numberControl(name: string, value: number, min: number, max: number, label?: string, step?: number): VectorControl;
|
|
113
119
|
|
|
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,
|
|
120
|
+
export { CURRENT_COLOR as C, 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, isCurrentColor as i, vectorEffectControls as j, numberControl as n, renderVectorEffect as r, vectorControlDef as v };
|
package/dist/vector/index.d.ts
CHANGED
|
@@ -38,6 +38,32 @@ declare function morphPaths(from: string, to: string, options?: MorphOptions): [
|
|
|
38
38
|
declare function parseViewBox(value: string | null | undefined): SvgBox | undefined;
|
|
39
39
|
/** The `viewBox` on an SVG's opening tag (see {@link parseViewBox}). */
|
|
40
40
|
declare function svgViewBox(markup: string): SvgBox | undefined;
|
|
41
|
+
/** How a picture framed for a line of text (a text effect's `inline`) sits in it; see {@link svgInlineLayout}. */
|
|
42
|
+
interface SvgInlineLayout {
|
|
43
|
+
/** Its viewBox: x, y, width, height. */
|
|
44
|
+
viewBox: SvgBox;
|
|
45
|
+
/** Width over height, for its host's CSS `aspect-ratio`. */
|
|
46
|
+
aspectRatio: number;
|
|
47
|
+
/**
|
|
48
|
+
* How far it reaches past its box at rest (descenders, outlines, glows,
|
|
49
|
+
* drips), in ems of the font size it is set at; 0 where the picture says
|
|
50
|
+
* nothing (no `data-overhang`).
|
|
51
|
+
*/
|
|
52
|
+
overhang: {
|
|
53
|
+
top: number;
|
|
54
|
+
right: number;
|
|
55
|
+
bottom: number;
|
|
56
|
+
left: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The box and overhang on an SVG's opening tag: its `viewBox`, and
|
|
61
|
+
* `data-overhang="top right bottom left"` (ems), which text effects write.
|
|
62
|
+
* Set inline (`height: 0.72em`, the cap height they frame), give the host a
|
|
63
|
+
* margin of the overhang to keep room for what spills out:
|
|
64
|
+
* `margin: ${top}em ${right}em ${bottom}em ${left}em`.
|
|
65
|
+
*/
|
|
66
|
+
declare function svgInlineLayout(markup: string): SvgInlineLayout | undefined;
|
|
41
67
|
interface SvgFrameMarkupOptions {
|
|
42
68
|
/**
|
|
43
69
|
* The time shown, in ms: a number, `"first"` (0) or `"last"` (the end of
|
|
@@ -113,4 +139,4 @@ interface FormatUnitOptions {
|
|
|
113
139
|
*/
|
|
114
140
|
declare function formatUnit(value: number, format?: UnitFormat, options?: FormatUnitOptions): string;
|
|
115
141
|
|
|
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 };
|
|
142
|
+
export { type BoxAlignment, type BoxFit, type FittedBoxResult, type FormatUnitOptions, type MorphOptions, type StackLayoutOptions, SvgBox, type SvgFrameMarkupOptions, type SvgInlineLayout, SvgMotion, type UnitFormat, computeBoxFit, formatUnit, layoutStack, morphPaths, morphSequence, parseViewBox, remap, stepVariant, svgFrameMarkup, svgInlineLayout, svgViewBox };
|
package/dist/vector/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
parseViewBox,
|
|
10
10
|
svgFrameMarkup,
|
|
11
|
+
svgInlineLayout,
|
|
11
12
|
svgViewBox
|
|
12
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-MILF5MRS.js";
|
|
13
14
|
import {
|
|
14
15
|
EASES,
|
|
15
16
|
SCRAMBLE_MS,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
svgOpeningTag,
|
|
32
33
|
svgView,
|
|
33
34
|
writtenText
|
|
34
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-RM3I6WZH.js";
|
|
35
36
|
import {
|
|
36
37
|
morphPaths,
|
|
37
38
|
morphSequence
|
|
@@ -63,6 +64,7 @@ export {
|
|
|
63
64
|
svgDuration,
|
|
64
65
|
svgFrame,
|
|
65
66
|
svgFrameMarkup,
|
|
67
|
+
svgInlineLayout,
|
|
66
68
|
svgMotionIssues,
|
|
67
69
|
svgOpeningTag,
|
|
68
70
|
svgView,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vector/static.ts"],"sourcesContent":["/**\n * An {@link SvgMotion}'s frame as markup, with no DOM: for server rendering\n * (a still that hydrates without a blank flash), reduced motion and other\n * places a live player is not wanted.\n */\n\nimport { SVG_PICTURE, svgFrame, type SvgBox, type SvgMotion } from \"./motion.js\";\nimport { svgDuration } from \"./svg.js\";\n\ninterface Node {\n tag: string;\n attrs: [string, string][];\n children: (Node | string)[];\n}\n\n/** Elements dropped with all they hold: they run code or show outside markup. */\nconst DROPPED = new Set([\"script\", \"foreignobject\", \"iframe\", \"object\", \"embed\"]);\nconst ATTR = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>\"']+)))?/g;\nconst TOKEN = /<!--[\\s\\S]*?(?:-->|$)|<!\\[CDATA\\[([\\s\\S]*?)(?:\\]\\]>|$)|<[!?][^>]*>|<\\/\\s*([\\w:.-]+)\\s*>|<([\\w:.-]+)((?:\\s+[^\\s=/>\"']+(?:\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>\"']+))?)*)\\s*(\\/?)>/g;\n\nconst ENTITIES: Record<string, string> = { amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\", nbsp: \" \" };\nconst decode = (s: string) =>\n s.replace(/&(#x[0-9a-f]+|#\\d+|\\w+);/gi, (match, e: string) => {\n if (e[0] !== \"#\") return ENTITIES[e.toLowerCase()] ?? match;\n const code = e[1] === \"x\" || e[1] === \"X\" ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10);\n return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : \"\";\n });\nconst escapeText = (s: string) => s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\nconst escapeAttr = (s: string) => escapeText(s).replace(/\"/g, \""\");\n\n/** Safe as `parseSvg` keeps it: no handlers, and links only within the picture or to data images. */\nfunction safeAttr(name: string, value: string): boolean {\n const n = name.toLowerCase();\n if (n.startsWith(\"on\") || !/^[a-z_][\\w:.-]*$/i.test(n)) return false;\n if (n === \"href\" || n === \"xlink:href\" || n === \"src\") return /^(#|data:image\\/)/i.test(value.trim());\n return true;\n}\n\n/** The `<svg>` in `markup` as a tree, without anything `parseSvg` would remove. */\nfunction parse(markup: string): Node | undefined {\n const root: Node = { tag: \"\", attrs: [], children: [] };\n const stack = [root];\n let dropping = 0;\n let at = 0;\n const text = (s: string) => {\n if (!dropping && s) stack[stack.length - 1]!.children.push(decode(s));\n };\n for (const m of markup.matchAll(TOKEN)) {\n text(markup.slice(at, m.index));\n at = m.index + m[0].length;\n const [, cdata, close, open, attrs = \"\", selfClosing] = m;\n if (cdata !== undefined) {\n if (!dropping) stack[stack.length - 1]!.children.push(cdata);\n } else if (close) {\n const tag = close.toLowerCase();\n if (dropping) {\n if (DROPPED.has(tag)) dropping--;\n continue;\n }\n // Close up to the matching element; a stray closing tag is ignored.\n let i = stack.length - 1;\n while (i > 0 && stack[i]!.tag.toLowerCase() !== tag) i--;\n if (i > 0) stack.length = i;\n } else if (open) {\n if (DROPPED.has(open.toLowerCase())) {\n if (!selfClosing) dropping++;\n continue;\n }\n if (dropping) continue;\n const node: Node = { tag: open, attrs: [], children: [] };\n for (const a of attrs.matchAll(ATTR)) {\n const value = decode(a[2] ?? a[3] ?? a[4] ?? \"\");\n if (safeAttr(a[1]!, value)) node.attrs.push([a[1]!, value]);\n }\n stack[stack.length - 1]!.children.push(node);\n if (!selfClosing) stack.push(node);\n }\n }\n text(markup.slice(at));\n const svg = root.children.find((c): c is Node => typeof c !== \"string\");\n return svg && svg.tag.toLowerCase() === \"svg\" ? svg : undefined;\n}\n\nfunction serialize(node: Node | string): string {\n if (typeof node === \"string\") return escapeText(node);\n const attrs = node.attrs.map(([k, v]) => ` ${k}=\"${escapeAttr(v)}\"`).join(\"\");\n return `<${node.tag}${attrs}>${node.children.map(serialize).join(\"\")}</${node.tag}>`;\n}\n\nfunction setAttr(node: Node, name: string, value: string | undefined) {\n const i = node.attrs.findIndex(([k]) => k === name);\n if (value === undefined) {\n if (i >= 0) node.attrs.splice(i, 1);\n } else if (!safeAttr(name, value)) return;\n else if (i >= 0) node.attrs[i]![1] = value;\n else node.attrs.push([name, value]);\n}\n\nconst getAttr = (node: Node, name: string) => node.attrs.find(([k]) => k === name)?.[1];\n\n/** A `viewBox` attribute's box, or undefined when it has none or it is malformed. */\nexport function parseViewBox(value: string | null | undefined): SvgBox | undefined {\n const box = (value ?? \"\").trim().split(/[\\s,]+/).map(Number);\n return box.length === 4 && box.every(Number.isFinite) && box[2]! > 0 && box[3]! > 0\n ? (box as unknown as SvgBox)\n : undefined;\n}\n\n/** The `viewBox` on an SVG's opening tag (see {@link parseViewBox}). */\nexport function svgViewBox(markup: string): SvgBox | undefined {\n const open = /<svg\\b[^>]*>/i.exec(markup)?.[0] ?? \"\";\n return parseViewBox(/\\sviewBox\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')/i.exec(open)?.slice(1).find((v) => v !== undefined));\n}\n\nexport interface SvgFrameMarkupOptions {\n /**\n * The time shown, in ms: a number, `\"first\"` (0) or `\"last\"` (the end of\n * its last change, see `svgDuration`; the default).\n */\n at?: number | \"first\" | \"last\";\n /** Step start times in ms, as a player's `steps` (default none). */\n steps?: readonly number[];\n}\n\n/**\n * The picture of an {@link SvgMotion} at a moment, as `<svg>` markup made safe\n * the way `parseSvg` makes it (no scripts, handlers, `<foreignObject>` or\n * outside links), with no DOM needed: render it on a server, show it under\n * `prefers-reduced-motion`, or anywhere a still will do. It is sized to fill\n * its container, as `SvgPlayer` draws it.\n *\n * It matches the player's frame except where that needs layout: a change\n * `along` a path, and a `view` that frames an element by id, stay where the\n * markup puts them. Undefined when `motion.svg` is not an `<svg>`.\n */\nexport function svgFrameMarkup(motion: SvgMotion, options: SvgFrameMarkupOptions = {}): string | undefined {\n const svg = parse(motion.svg.trim());\n if (!svg) return undefined;\n const steps = options.steps ?? [];\n const at = options.at ?? \"last\";\n const time = at === \"first\" ? 0 : at === \"last\" ? svgDuration(motion, steps) : at;\n const byId = new Map<string, Node>();\n const index = (node: Node) => {\n const id = getAttr(node, \"id\");\n if (id && !byId.has(id)) byId.set(id, node);\n for (const c of node.children) if (typeof c !== \"string\") index(c);\n };\n index(svg);\n const whole = parseViewBox(getAttr(svg, \"viewBox\"));\n for (const [id, frame] of svgFrame(motion.animate ?? [], steps, time)) {\n if (id === SVG_PICTURE && frame.view) {\n const { from, to, u } = frame.view;\n const a = from === null ? whole : typeof from === \"string\" ? undefined : from;\n const b = typeof to === \"string\" ? undefined : to;\n if (a && b) setAttr(svg, \"viewBox\", a.map((n, i) => +(n + (b[i]! - n) * u).toFixed(2)).join(\" \"));\n continue;\n }\n const el = byId.get(id);\n if (!el) continue;\n for (const [name, value] of Object.entries(frame.attrs)) setAttr(el, name, value);\n if (frame.draw !== undefined) {\n setAttr(el, \"pathLength\", \"1\");\n setAttr(el, \"stroke-dasharray\", \"1 1\");\n setAttr(el, \"stroke-dashoffset\", String(1 - frame.draw));\n }\n if (frame.text !== undefined) el.children = [frame.text];\n if (frame.solo !== undefined) {\n let i = 0;\n for (const child of el.children)\n if (typeof child !== \"string\") setAttr(child, \"display\", i++ === frame.solo ? undefined : \"none\");\n }\n }\n setAttr(svg, \"width\", \"100%\");\n setAttr(svg, \"height\", \"100%\");\n if (!getAttr(svg, \"xmlns\")) setAttr(svg, \"xmlns\", \"http://www.w3.org/2000/svg\");\n return serialize(svg);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shader/controls.ts"],"sourcesContent":["/**\n * Controls declared in a shader's source. A uniform becomes a control when\n * its declaration carries an `@control` comment on the same line:\n *\n * uniform float uSpeed; // @control min=0 max=4 default=1 label=\"Speed\"\n * uniform int uSteps; // @control min=1 max=64 default=16\n * uniform bool uGrain; // @control default=true label=\"Film grain\"\n * uniform vec2 uCenter; // @control min=-1 max=1 default=0,0\n * uniform vec3 uTint; // @control color default=#77C16A label=\"Tint\"\n * uniform sampler2D uPhoto; // @control filter=linear wrap=clamp label=\"Photo\"\n *\n * The source is the single source of truth for which controls exist and\n * their defaults; values someone picks are stored next to it.\n */\n\n/** The uniform types a control can have. */\nexport type ControlType = \"float\" | \"int\" | \"bool\" | \"vec2\" | \"vec3\" | \"vec4\" | \"sampler2D\" | \"text\";\n\n/**\n * A control's value: a number (`float`, `int`), a boolean (`bool`), the\n * components of a vector, an image URL (`sampler2D`; \"\" for none), or a\n * string (`text`, vector effects only: shaders cannot take text).\n */\nexport type ControlValue = number | boolean | number[] | string;\n\n/** How an image is sampled: Shadertoy's channel settings. */\nexport interface TextureSettings {\n /** `mipmap` (the default), `linear` or `nearest` (pixelated). */\n filter: \"mipmap\" | \"linear\" | \"nearest\";\n /** Outside 0–1: `repeat` (the default), `clamp` to the edge, or `mirror`. */\n wrap: \"repeat\" | \"clamp\" | \"mirror\";\n}\n\n/** A control parsed from an annotated uniform. */\nexport interface ControlDef {\n /** The uniform's name. */\n name: string;\n type: ControlType;\n /** `label=\"…\"`, or the name made readable (\"uFlowSpeed\" → \"Flow speed\"). */\n label: string;\n /** A `vec3` or `vec4` shown as a colour picker. */\n color: boolean;\n min: number;\n /** Always greater than `min`. */\n max: number;\n step: number;\n default: ControlValue;\n /** 1-based line of the declaration. */\n line: number;\n /** Sampling settings, for `sampler2D` controls only. */\n texture?: TextureSettings;\n}\n\nconst DECLARATION =\n /^\\s*uniform\\s+(float|int|bool|vec2|vec3|vec4|sampler2D)\\s+([A-Za-z_]\\w*)\\s*;\\s*\\/\\/\\s*@control\\b(.*)$/;\nconst ATTRIBUTE = /([a-z]+)(?:=(\"[^\"]*\"|'[^']*'|\\S+))?/gi;\n\nconst COMPONENTS: Record<ControlType, number> = {\n float: 1,\n int: 1,\n bool: 1,\n vec2: 2,\n vec3: 3,\n vec4: 4,\n sampler2D: 1,\n text: 1,\n};\n\n/** The number of components a control type has: 1 for scalars, `bool`, `sampler2D` and `text`, else the vector size. */\nexport function componentCount(type: ControlType): number {\n return COMPONENTS[type];\n}\n\n/** \"uFlowSpeed\" → \"Flow speed\". */\nexport function humanise(name: string): string {\n const words = name\n .replace(/^u(?=[A-Z])/, \"\")\n .replace(/_/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .trim()\n .toLowerCase();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\n/** \"#77C16A\" (or \"#7c6\") → [0.467, 0.757, 0.416]; null when it is not a hex colour. */\nexport function hexToRgb(hex: string): number[] | null {\n const m = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i.exec(hex.trim());\n if (!m) return null;\n const digits = m[1]!;\n const h =\n digits.length === 3\n ? digits\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : digits;\n return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16) / 255);\n}\n\n/** [0.467, 0.757, 0.416] → \"#77c16a\". Components are clamped to 0–1; a fourth (alpha) is ignored. */\nexport function rgbToHex(rgb: number[]): string {\n return (\n \"#\" +\n rgb\n .slice(0, 3)\n .map((c) =>\n Math.round(Math.min(1, Math.max(0, c)) * 255)\n .toString(16)\n .padStart(2, \"0\"),\n )\n .join(\"\")\n );\n}\n\nfunction parseDefault(raw: string | undefined, type: ControlType, color: boolean): ControlValue {\n const n = COMPONENTS[type];\n if (type === \"bool\") return raw === \"true\" || raw === \"1\";\n if (type === \"sampler2D\" || type === \"text\") return raw ?? \"\";\n // Without a default, numbers start at 0 and colours at white.\n if (raw === undefined) return n === 1 ? 0 : Array.from({ length: n }, () => (color ? 1 : 0));\n if (color && raw.startsWith(\"#\")) {\n const rgb = hexToRgb(raw) ?? [1, 1, 1];\n return type === \"vec4\" ? [...rgb, 1] : rgb;\n }\n const parts = raw\n .split(\",\")\n .map(Number)\n .map((v) => (Number.isFinite(v) ? v : 0));\n if (n === 1) return parts[0] ?? 0;\n // A short list repeats its last component: `default=0.5` on a vec3 is (0.5, 0.5, 0.5).\n return Array.from({ length: n }, (_, i) => parts[i] ?? parts[parts.length - 1] ?? 0);\n}\n\n/**\n * A control from its declaration's attributes (the text after `@control`):\n * `min`, `max`, `step`, `default`, `label`, `color`, and for images `filter`\n * and `wrap`. Values with spaces are quoted, in double or single quotes.\n * Shared by shader sources and vector effects' `{{name: type …}}` placeholders.\n */\nexport function controlFromDeclaration(name: string, type: ControlType, attributes: string, line: number): ControlDef {\n const attrs: Record<string, string | undefined> = {};\n for (const a of attributes.matchAll(ATTRIBUTE)) {\n attrs[a[1]!.toLowerCase()] = a[2]?.replace(/^([\"'])(.*)\\1$/s, \"$2\");\n }\n const color = \"color\" in attrs && (type === \"vec3\" || type === \"vec4\");\n const num = (v: string | undefined, fallback: number) =>\n v !== undefined && Number.isFinite(Number(v)) ? Number(v) : fallback;\n const min = num(attrs.min, 0);\n const rawMax = num(attrs.max, type === \"int\" ? 10 : 1);\n const max = rawMax > min ? rawMax : min + 1;\n const control: ControlDef = {\n name,\n type,\n label: attrs.label ?? humanise(name),\n color,\n min,\n max,\n step: num(attrs.step, type === \"int\" ? 1 : (max - min) / 200),\n default: parseDefault(attrs.default, type, color),\n line,\n };\n if (type === \"sampler2D\") {\n const pick = <T extends string>(v: string | undefined, options: readonly T[]): T =>\n options.find((o) => o === v?.toLowerCase()) ?? options[0]!;\n control.texture = {\n filter: pick(attrs.filter, [\"mipmap\", \"linear\", \"nearest\"] as const),\n wrap: pick(attrs.wrap, [\"repeat\", \"clamp\", \"mirror\"] as const),\n };\n }\n return control;\n}\n\n/**\n * The controls a source declares, in source order. A uniform declared twice\n * counts once (the first wins). Unknown attributes are ignored, and missing\n * or malformed ones fall back to defaults: `min=0`, `max=1` (10 for `int`),\n * a step of 1/200 of the range (1 for `int`).\n */\nexport function parseControls(source: string): ControlDef[] {\n const controls: ControlDef[] = [];\n const seen = new Set<string>();\n source.split(\"\\n\").forEach((text, index) => {\n const m = DECLARATION.exec(text);\n if (!m || seen.has(m[2]!)) return;\n seen.add(m[2]!);\n controls.push(controlFromDeclaration(m[2]!, m[1] as ControlType, m[3]!, index + 1));\n });\n return controls;\n}\n\n/** The value a control has: the stored one when it still fits the declaration, else the default. */\nexport function controlValue(\n control: ControlDef,\n values: Record<string, ControlValue> | undefined,\n): ControlValue {\n const v = values?.[control.name];\n if (v === undefined) return control.default;\n if (control.type === \"bool\") return typeof v === \"boolean\" ? v : control.default;\n if (control.type === \"sampler2D\" || control.type === \"text\") return typeof v === \"string\" ? v : control.default;\n const n = COMPONENTS[control.type];\n if (n === 1) return typeof v === \"number\" ? v : control.default;\n return Array.isArray(v) && v.length === n ? v : control.default;\n}\n\n/** What {@link checkControlValue} decides: the value to store, or a sentence saying what the control takes. */\nexport type ControlCheck = { ok: true; value: ControlValue } | { ok: false; message: string };\n\n/**\n * Checks a value from outside (an agent's tool call, a URL, a form) against a\n * control. Colours also accept \"#RRGGBB\"; `int` values are rounded. Images\n * take a URL: `https:`, `http:`, `data:image/…`, `blob:` or a relative path\n * (\"\" for none).\n */\nexport function checkControlValue(control: ControlDef, raw: unknown): ControlCheck {\n const fail = (message: string): ControlCheck => ({ ok: false, message });\n const n = COMPONENTS[control.type];\n if (control.type === \"bool\") {\n return typeof raw === \"boolean\"\n ? { ok: true, value: raw }\n : fail(`${control.name} takes true or false.`);\n }\n if (control.type === \"sampler2D\") {\n return typeof raw === \"string\" && (raw === \"\" || isImageUrl(raw))\n ? { ok: true, value: raw }\n : fail(`${control.name} takes an image URL: https://…, data:image/… or blob:….`);\n }\n if (control.type === \"text\") {\n return typeof raw === \"string\" ? { ok: true, value: raw } : fail(`${control.name} takes text.`);\n }\n if (control.color && typeof raw === \"string\") {\n const rgb = hexToRgb(raw);\n if (!rgb) return fail(`${control.name} takes a colour like \"#77C16A\".`);\n return { ok: true, value: control.type === \"vec4\" ? [...rgb, 1] : rgb };\n }\n if (n === 1) {\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) {\n return fail(`${control.name} takes a number.`);\n }\n return { ok: true, value: control.type === \"int\" ? Math.round(raw) : raw };\n }\n if (\n !Array.isArray(raw) ||\n raw.length !== n ||\n !raw.every((x) => typeof x === \"number\" && Number.isFinite(x))\n ) {\n return fail(\n `${control.name} takes ${n} numbers${control.color ? ' (or a colour like \"#77C16A\")' : \"\"}.`,\n );\n }\n return { ok: true, value: raw as number[] };\n}\n\n/**\n * {@link checkControlValue} for controls whose values are never strings:\n * returns the value to store, or the message. An image control's value is a\n * string too, so use `checkControlValue` where images can appear.\n */\nexport function coerceControlValue(control: ControlDef, raw: unknown): ControlValue | string {\n const check = checkControlValue(control, raw);\n return check.ok ? check.value : check.message;\n}\n\n/** Whether a string is a URL an image control can load: `http(s):`, `data:image/`, `blob:` or a relative path. */\nexport function isImageUrl(url: string): boolean {\n const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url.trim())?.[1]?.toLowerCase();\n if (scheme === undefined) return url.trim() !== \"\";\n if (scheme === \"data\") return /^data:image\\//i.test(url.trim());\n return scheme === \"http\" || scheme === \"https\" || scheme === \"blob\";\n}\n\n/** The image URLs the controls show with these values, without duplicates or empty ones. */\nexport function imageUrls(\n controls: ControlDef[],\n values: Record<string, ControlValue> | undefined,\n): string[] {\n const urls = new Set<string>();\n for (const control of controls) {\n if (control.type !== \"sampler2D\") continue;\n const url = controlValue(control, values) as string;\n if (url) urls.add(url);\n }\n return [...urls];\n}\n\n/**\n * A value as compact, JSON-friendly data: colours as hex, numbers to three\n * decimals. A long `data:` URL is shortened to its type and size, which\n * keeps it out of an agent's context but cannot be stored back.\n */\nexport function describeValue(control: ControlDef, value: ControlValue): unknown {\n if (typeof value === \"string\" && value.startsWith(\"data:\") && value.length > 100) {\n const kb = Math.round((value.length * 3) / 4 / 1024);\n return `${value.slice(0, value.indexOf(\",\") + 1)}… (about ${kb} kB)`;\n }\n if (control.color && Array.isArray(value)) return rgbToHex(value);\n if (Array.isArray(value)) return value.map((v) => Math.round(v * 1000) / 1000);\n if (typeof value === \"number\") return Math.round(value * 1000) / 1000;\n return value;\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|