@pixerate/editor-react 0.78.1 → 0.79.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 @@
1
+ {"version":3,"sources":["../src/effects/useEffects.ts","../src/effects/EffectThumbnail.tsx","../src/effects/EffectsBrowser.tsx","../src/effects/EffectParameters.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport type { EffectDescription, EffectProvider } from '@pixerate/editor/effects';\n\n/** Anything that takes an effects provider: every editor controller with effects support. */\nexport interface EffectsHost {\n useEffects(provider: EffectProvider): () => void;\n}\n\n/**\n * Registers `provider` with an editor while mounted (or while it is the one passed), and removes it\n * again on unmount. Pass `null` or `undefined` for no provider: the editor then behaves as without\n * effects support.\n */\nexport function useEffects(host: EffectsHost | null | undefined, provider: EffectProvider | null | undefined): void {\n useEffect(() => {\n if (!host || !provider) return;\n return host.useEffects(provider);\n }, [host, provider]);\n}\n\n/** An effect's description from the provider, loaded when `effect` changes (`null` while loading or on error). */\nexport function useEffectDescription(provider: EffectProvider | null | undefined, effect: string | null | undefined): { description: EffectDescription | null; error: string | null } {\n const [state, setState] = useState<{ key: string; description: EffectDescription | null; error: string | null }>({ key: '', description: null, error: null });\n const key = provider && effect ? `${provider.id}:${effect}` : '';\n useEffect(() => {\n if (!provider || !effect) return;\n let live = true;\n Promise.resolve()\n .then(() => provider.describe(effect))\n .then(\n (description) => live && setState({ key, description, error: null }),\n (e: unknown) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) }),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, key]);\n return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport { renderEffectThumbnail, type EffectProvider, type EffectSummary } from '@pixerate/editor/effects';\n\nexport interface EffectThumbnailProps {\n provider: EffectProvider;\n effect: EffectSummary;\n /** Pixel size of the preview (default 96 × 54). */\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A preview of an effect drawn by the provider's own renderer (filters on a sample picture,\n * transitions half way, generators mid-way through). Motion effects and failures show a label.\n */\nexport function EffectThumbnail({ provider, effect, width = 96, height = 54, className = '' }: EffectThumbnailProps) {\n const ref = useRef<HTMLCanvasElement>(null);\n const [failed, setFailed] = useState(effect.kind === 'motion');\n useEffect(() => {\n if (effect.kind === 'motion') return;\n let live = true;\n renderEffectThumbnail(provider, effect, { width, height }).then(\n (pixels) => {\n const ctx = live ? ref.current?.getContext('2d') : null;\n if (!ctx || typeof ImageData === 'undefined') return;\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);\n },\n () => live && setFailed(true),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, width, height]);\n return (\n <div\n className={`relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`}\n style={{ aspectRatio: `${width} / ${height}` }}\n data-effect-thumbnail={effect.id}\n >\n {failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground\">{effect.kind === 'motion' ? 'Motion' : 'No preview'}</span>\n ) : (\n <canvas ref={ref} width={width} height={height} className=\"h-full w-full\" aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { EFFECT_USE_LABELS, type EffectKind, type EffectProvider, type EffectSummary, type EffectUse } from '@pixerate/editor/effects';\nimport { EffectThumbnail } from './EffectThumbnail';\n\nexport interface EffectsBrowserProps {\n /** The editor's provider; without one the browser says effects are unavailable. */\n provider: EffectProvider | null | undefined;\n /** Only effects with one of these uses (default: all but motion). */\n uses?: readonly EffectUse[];\n /** Only effects of these kinds (default: any). */\n kinds?: readonly EffectKind[];\n /** Called with the effect the user picks. */\n onPick: (effect: EffectSummary) => void;\n /** Draw previews with the provider's renderer (default true). */\n thumbnails?: boolean;\n /** Heading (default \"Effects\"). */\n title?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Browses a provider's effects: a search box, a category filter (and a use filter when more than\n * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.\n */\nexport function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = 'Effects', className = '' }: EffectsBrowserProps) {\n const [all, setAll] = useState<readonly EffectSummary[]>([]);\n const [query, setQuery] = useState('');\n const [category, setCategory] = useState('');\n const [use, setUse] = useState<EffectUse | ''>('');\n const [error, setError] = useState<string | null>(null);\n useEffect(() => {\n if (!provider) return setAll([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.list())\n .then(\n (list) => live && (setAll(list), setError(null)),\n (e: unknown) => live && setError(e instanceof Error ? e.message : String(e)),\n );\n return () => {\n live = false;\n };\n }, [provider]);\n const [found, setFound] = useState<readonly EffectSummary[]>([]);\n useEffect(() => {\n if (!provider) return setFound([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.search(query, { ...(category ? { category } : {}), ...(use ? { use } : {}) }))\n .then((list) => live && setFound(list), () => live && setFound([]));\n return () => {\n live = false;\n };\n }, [provider, query, category, use]);\n const allowed = (e: EffectSummary) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== 'motion' || uses.includes('motion') : e.kind !== 'motion') && (!uses || e.uses.some((u) => uses.includes(u)));\n const categories = useMemo(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);\n const shown = found.filter(allowed);\n if (!provider)\n return (\n <div className={`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`} data-effects-browser=\"\" data-effects-unavailable=\"\">\n No effects provider is registered, so effects cannot be added. Effects already in the document are kept.\n </div>\n );\n return (\n <section className={`flex flex-col gap-2 ${className}`} data-effects-browser=\"\" aria-label=\"Effects browser\">\n {title && <h3 className=\"text-xs font-semibold\">{title}</h3>}\n <div className=\"flex flex-wrap gap-1\">\n <input\n type=\"search\"\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs\"\n placeholder=\"Search effects\"\n aria-label=\"Search effects\"\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Category\" value={category} onChange={(e) => setCategory(e.currentTarget.value)}>\n <option value=\"\">All categories</option>\n {categories.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n {uses && uses.length > 1 && (\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Use\" value={use} onChange={(e) => setUse(e.currentTarget.value as EffectUse | '')}>\n <option value=\"\">Any use</option>\n {uses.map((u) => (\n <option key={u} value={u}>\n {EFFECT_USE_LABELS[u]}\n </option>\n ))}\n </select>\n )}\n </div>\n {error && <p role=\"alert\" className=\"text-[11px] text-destructive\">{error}</p>}\n <ul className=\"grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto\" role=\"list\">\n {shown.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className=\"flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border\"\n title={e.description ?? e.name}\n data-effect-id={e.id}\n onClick={() => onPick(e)}\n >\n {thumbnails && <EffectThumbnail provider={provider} effect={e} />}\n <span className=\"truncate text-[11px] font-medium\">{e.name}</span>\n <span className=\"truncate text-[10px] text-muted-foreground\">{e.uses.map((u) => EFFECT_USE_LABELS[u]).join(' · ')}</span>\n </button>\n </li>\n ))}\n </ul>\n {!shown.length && !error && <p className=\"text-[11px] text-muted-foreground\">No effects match.</p>}\n </section>\n );\n}\n","import React, { useId, useMemo } from 'react';\nimport { effectFieldValue, effectParameterFields, type EffectDescription } from '@pixerate/editor/effects';\nimport { parameterLabel, parameterValuesEqual, type ParameterValue } from '@pixerate/editor/parameters';\nimport { ParameterFieldControl } from '../parameters/fields';\n\nexport interface EffectParametersProps {\n /** The effect's description (`describe`). */\n description: EffectDescription;\n /** Its saved values; controls left out show their defaults. */\n values: Readonly<Record<string, unknown>>;\n /**\n * A control changed. While a slider or colour drag is under way `gesture` is a key unique to\n * that drag, so the host can fold its changes into one undo step; otherwise `null`.\n */\n onChange: (control: string, value: ParameterValue, gesture: string | null) => void;\n /** Resets one control to its default. */\n onReset?: (control: string) => void;\n /** Controls that are keyframed: shown read-only with a note (edit them as keyframes). */\n keyframed?: ReadonlySet<string>;\n /** Applies a preset's values. */\n onPreset?: (values: Readonly<Record<string, unknown>>) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The parameter panel of an effect, generated from its JSON Schema controls: sliders for bounded\n * numbers, switches, choices, colour pickers, vectors and text, with each control's description,\n * per-control reset and the effect's presets.\n */\nexport function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = '' }: EffectParametersProps) {\n const fields = useMemo(() => effectParameterFields(description), [description]);\n const idBase = useId();\n const gesture = React.useRef<string | null>(null);\n const count = React.useRef(0);\n return (\n <div className={`space-y-2 ${className}`} data-effect-parameters={description.id}>\n {onPreset && description.presets.length > 0 && (\n <select\n className=\"w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n aria-label={`${description.name} presets`}\n value=\"\"\n disabled={disabled}\n onChange={(e) => {\n const preset = description.presets.find((p) => p.name === e.currentTarget.value);\n if (preset) onPreset({ ...description.defaults, ...preset.values });\n }}\n >\n <option value=\"\">Presets…</option>\n {description.presets.map((p) => (\n <option key={p.name} value={p.name}>\n {p.name}\n </option>\n ))}\n </select>\n )}\n {fields.map((field) => {\n const label = parameterLabel(field);\n const inputId = `${idBase}-${field.id}`;\n const keyed = keyframed?.has(field.id) ?? false;\n const value = effectFieldValue(field, values[field.id]);\n const changed = !keyed && !parameterValuesEqual(value, field.default);\n return (\n <div key={field.id} className=\"space-y-1\" data-effect-control={field.id}>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor={inputId} className=\"min-w-0 flex-1 truncate text-xs font-medium\" title={field.description}>\n {label}\n </label>\n {keyed && <span className=\"text-[10px] text-muted-foreground\">keyframed</span>}\n {onReset && changed && !disabled && (\n <button type=\"button\" className=\"rounded px-1 text-[11px] text-muted-foreground hover:text-foreground\" aria-label={`Reset ${label}`} onClick={() => onReset(field.id)}>\n ↺\n </button>\n )}\n </div>\n <ParameterFieldControl\n field={field}\n value={value}\n id={inputId}\n disabled={disabled || keyed}\n onChange={(v) => onChange(field.id, v, gesture.current)}\n onGestureStart={() => (gesture.current = `${idBase}:${field.id}:${++count.current}`)}\n onGestureEnd={() => (gesture.current = null)}\n />\n </div>\n );\n })}\n {!fields.length && <p className=\"text-[11px] text-muted-foreground\">No controls.</p>}\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","useEffect","useState","jsx","useState","useEffect","React","useMemo","jsx","jsxs","useMemo","React"]}
@@ -100,7 +100,7 @@ function EffectThumbnail({ provider, effect, width = 96, height = 54, className
100
100
  var import_react3 = require("react");
101
101
  var import_effects2 = require("@pixerate/editor/effects");
102
102
  var import_jsx_runtime2 = require("react/jsx-runtime");
103
- function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Effects", className = "" }) {
103
+ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = "Effects", className = "" }) {
104
104
  const [all, setAll] = (0, import_react3.useState)([]);
105
105
  const [query, setQuery] = (0, import_react3.useState)("");
106
106
  const [category, setCategory] = (0, import_react3.useState)("");
@@ -126,8 +126,8 @@ function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Ef
126
126
  live = false;
127
127
  };
128
128
  }, [provider, query, category, use]);
129
- const allowed = (e) => (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
130
- const categories = (0, import_react3.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);
129
+ const allowed = (e) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
130
+ const categories = (0, import_react3.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
131
131
  const shown = found.filter(allowed);
132
132
  if (!provider)
133
133
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/effects/index.ts","../../src/effects/useEffects.ts","../../src/effects/EffectThumbnail.tsx","../../src/effects/EffectsBrowser.tsx","../../src/effects/EffectParameters.tsx","../../src/parameters/fields.tsx"],"sourcesContent":["export * from './useEffects';\nexport * from './EffectThumbnail';\nexport * from './EffectsBrowser';\nexport * from './EffectParameters';\n","import { useEffect, useState } from 'react';\nimport type { EffectDescription, EffectProvider } from '@pixerate/editor/effects';\n\n/** Anything that takes an effects provider: every editor controller with effects support. */\nexport interface EffectsHost {\n useEffects(provider: EffectProvider): () => void;\n}\n\n/**\n * Registers `provider` with an editor while mounted (or while it is the one passed), and removes it\n * again on unmount. Pass `null` or `undefined` for no provider: the editor then behaves as without\n * effects support.\n */\nexport function useEffects(host: EffectsHost | null | undefined, provider: EffectProvider | null | undefined): void {\n useEffect(() => {\n if (!host || !provider) return;\n return host.useEffects(provider);\n }, [host, provider]);\n}\n\n/** An effect's description from the provider, loaded when `effect` changes (`null` while loading or on error). */\nexport function useEffectDescription(provider: EffectProvider | null | undefined, effect: string | null | undefined): { description: EffectDescription | null; error: string | null } {\n const [state, setState] = useState<{ key: string; description: EffectDescription | null; error: string | null }>({ key: '', description: null, error: null });\n const key = provider && effect ? `${provider.id}:${effect}` : '';\n useEffect(() => {\n if (!provider || !effect) return;\n let live = true;\n Promise.resolve()\n .then(() => provider.describe(effect))\n .then(\n (description) => live && setState({ key, description, error: null }),\n (e: unknown) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) }),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, key]);\n return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport { renderEffectThumbnail, type EffectProvider, type EffectSummary } from '@pixerate/editor/effects';\n\nexport interface EffectThumbnailProps {\n provider: EffectProvider;\n effect: EffectSummary;\n /** Pixel size of the preview (default 96 × 54). */\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A preview of an effect drawn by the provider's own renderer (filters on a sample picture,\n * transitions half way, generators mid-way through). Motion effects and failures show a label.\n */\nexport function EffectThumbnail({ provider, effect, width = 96, height = 54, className = '' }: EffectThumbnailProps) {\n const ref = useRef<HTMLCanvasElement>(null);\n const [failed, setFailed] = useState(effect.kind === 'motion');\n useEffect(() => {\n if (effect.kind === 'motion') return;\n let live = true;\n renderEffectThumbnail(provider, effect, { width, height }).then(\n (pixels) => {\n const ctx = live ? ref.current?.getContext('2d') : null;\n if (!ctx || typeof ImageData === 'undefined') return;\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);\n },\n () => live && setFailed(true),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, width, height]);\n return (\n <div\n className={`relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`}\n style={{ aspectRatio: `${width} / ${height}` }}\n data-effect-thumbnail={effect.id}\n >\n {failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground\">{effect.kind === 'motion' ? 'Motion' : 'No preview'}</span>\n ) : (\n <canvas ref={ref} width={width} height={height} className=\"h-full w-full\" aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { EFFECT_USE_LABELS, type EffectProvider, type EffectSummary, type EffectUse } from '@pixerate/editor/effects';\nimport { EffectThumbnail } from './EffectThumbnail';\n\nexport interface EffectsBrowserProps {\n /** The editor's provider; without one the browser says effects are unavailable. */\n provider: EffectProvider | null | undefined;\n /** Only effects with one of these uses (default: all but motion). */\n uses?: readonly EffectUse[];\n /** Called with the effect the user picks. */\n onPick: (effect: EffectSummary) => void;\n /** Draw previews with the provider's renderer (default true). */\n thumbnails?: boolean;\n /** Heading (default \"Effects\"). */\n title?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Browses a provider's effects: a search box, a category filter (and a use filter when more than\n * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.\n */\nexport function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = 'Effects', className = '' }: EffectsBrowserProps) {\n const [all, setAll] = useState<readonly EffectSummary[]>([]);\n const [query, setQuery] = useState('');\n const [category, setCategory] = useState('');\n const [use, setUse] = useState<EffectUse | ''>('');\n const [error, setError] = useState<string | null>(null);\n useEffect(() => {\n if (!provider) return setAll([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.list())\n .then(\n (list) => live && (setAll(list), setError(null)),\n (e: unknown) => live && setError(e instanceof Error ? e.message : String(e)),\n );\n return () => {\n live = false;\n };\n }, [provider]);\n const [found, setFound] = useState<readonly EffectSummary[]>([]);\n useEffect(() => {\n if (!provider) return setFound([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.search(query, { ...(category ? { category } : {}), ...(use ? { use } : {}) }))\n .then((list) => live && setFound(list), () => live && setFound([]));\n return () => {\n live = false;\n };\n }, [provider, query, category, use]);\n const allowed = (e: EffectSummary) => (uses ? e.kind !== 'motion' || uses.includes('motion') : e.kind !== 'motion') && (!uses || e.uses.some((u) => uses.includes(u)));\n const categories = useMemo(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);\n const shown = found.filter(allowed);\n if (!provider)\n return (\n <div className={`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`} data-effects-browser=\"\" data-effects-unavailable=\"\">\n No effects provider is registered, so effects cannot be added. Effects already in the document are kept.\n </div>\n );\n return (\n <section className={`flex flex-col gap-2 ${className}`} data-effects-browser=\"\" aria-label=\"Effects browser\">\n {title && <h3 className=\"text-xs font-semibold\">{title}</h3>}\n <div className=\"flex flex-wrap gap-1\">\n <input\n type=\"search\"\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs\"\n placeholder=\"Search effects\"\n aria-label=\"Search effects\"\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Category\" value={category} onChange={(e) => setCategory(e.currentTarget.value)}>\n <option value=\"\">All categories</option>\n {categories.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n {uses && uses.length > 1 && (\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Use\" value={use} onChange={(e) => setUse(e.currentTarget.value as EffectUse | '')}>\n <option value=\"\">Any use</option>\n {uses.map((u) => (\n <option key={u} value={u}>\n {EFFECT_USE_LABELS[u]}\n </option>\n ))}\n </select>\n )}\n </div>\n {error && <p role=\"alert\" className=\"text-[11px] text-destructive\">{error}</p>}\n <ul className=\"grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto\" role=\"list\">\n {shown.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className=\"flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border\"\n title={e.description ?? e.name}\n data-effect-id={e.id}\n onClick={() => onPick(e)}\n >\n {thumbnails && <EffectThumbnail provider={provider} effect={e} />}\n <span className=\"truncate text-[11px] font-medium\">{e.name}</span>\n <span className=\"truncate text-[10px] text-muted-foreground\">{e.uses.map((u) => EFFECT_USE_LABELS[u]).join(' · ')}</span>\n </button>\n </li>\n ))}\n </ul>\n {!shown.length && !error && <p className=\"text-[11px] text-muted-foreground\">No effects match.</p>}\n </section>\n );\n}\n","import React, { useId, useMemo } from 'react';\nimport { effectFieldValue, effectParameterFields, type EffectDescription } from '@pixerate/editor/effects';\nimport { parameterLabel, parameterValuesEqual, type ParameterValue } from '@pixerate/editor/parameters';\nimport { ParameterFieldControl } from '../parameters/fields';\n\nexport interface EffectParametersProps {\n /** The effect's description (`describe`). */\n description: EffectDescription;\n /** Its saved values; controls left out show their defaults. */\n values: Readonly<Record<string, unknown>>;\n /**\n * A control changed. While a slider or colour drag is under way `gesture` is a key unique to\n * that drag, so the host can fold its changes into one undo step; otherwise `null`.\n */\n onChange: (control: string, value: ParameterValue, gesture: string | null) => void;\n /** Resets one control to its default. */\n onReset?: (control: string) => void;\n /** Controls that are keyframed: shown read-only with a note (edit them as keyframes). */\n keyframed?: ReadonlySet<string>;\n /** Applies a preset's values. */\n onPreset?: (values: Readonly<Record<string, unknown>>) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The parameter panel of an effect, generated from its JSON Schema controls: sliders for bounded\n * numbers, switches, choices, colour pickers, vectors and text, with each control's description,\n * per-control reset and the effect's presets.\n */\nexport function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = '' }: EffectParametersProps) {\n const fields = useMemo(() => effectParameterFields(description), [description]);\n const idBase = useId();\n const gesture = React.useRef<string | null>(null);\n const count = React.useRef(0);\n return (\n <div className={`space-y-2 ${className}`} data-effect-parameters={description.id}>\n {onPreset && description.presets.length > 0 && (\n <select\n className=\"w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n aria-label={`${description.name} presets`}\n value=\"\"\n disabled={disabled}\n onChange={(e) => {\n const preset = description.presets.find((p) => p.name === e.currentTarget.value);\n if (preset) onPreset({ ...description.defaults, ...preset.values });\n }}\n >\n <option value=\"\">Presets…</option>\n {description.presets.map((p) => (\n <option key={p.name} value={p.name}>\n {p.name}\n </option>\n ))}\n </select>\n )}\n {fields.map((field) => {\n const label = parameterLabel(field);\n const inputId = `${idBase}-${field.id}`;\n const keyed = keyframed?.has(field.id) ?? false;\n const value = effectFieldValue(field, values[field.id]);\n const changed = !keyed && !parameterValuesEqual(value, field.default);\n return (\n <div key={field.id} className=\"space-y-1\" data-effect-control={field.id}>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor={inputId} className=\"min-w-0 flex-1 truncate text-xs font-medium\" title={field.description}>\n {label}\n </label>\n {keyed && <span className=\"text-[10px] text-muted-foreground\">keyframed</span>}\n {onReset && changed && !disabled && (\n <button type=\"button\" className=\"rounded px-1 text-[11px] text-muted-foreground hover:text-foreground\" aria-label={`Reset ${label}`} onClick={() => onReset(field.id)}>\n ↺\n </button>\n )}\n </div>\n <ParameterFieldControl\n field={field}\n value={value}\n id={inputId}\n disabled={disabled || keyed}\n onChange={(v) => onChange(field.id, v, gesture.current)}\n onGestureStart={() => (gesture.current = `${idBase}:${field.id}:${++count.current}`)}\n onGestureEnd={() => (gesture.current = null)}\n />\n </div>\n );\n })}\n {!fields.length && <p className=\"text-[11px] text-muted-foreground\">No controls.</p>}\n </div>\n );\n}\n","import React, { useRef } from 'react';\nimport {\n choiceOptionLabel,\n formatParameterNumber,\n getChoiceOptions,\n isSliderField,\n normalizeHexColor,\n parameterLabel,\n parameterStep,\n parseParameterNumberInput,\n clampParameterNumber,\n vectorComponentField,\n vectorSize,\n type BooleanParameterField,\n type ChoiceParameterField,\n type ColorParameterField,\n type ImageParameterField,\n type NumberParameterField,\n type ParameterField,\n type ParameterKeyState,\n type ParameterValue,\n type TextParameterField,\n type VectorParameterField,\n} from '@pixerate/editor/parameters';\n\n/** Props every field control takes. Controls are controller-free: wire them to anything. */\nexport interface ParameterControlProps<F extends ParameterField, V extends ParameterValue> {\n field: F;\n value: V;\n onChange: (value: V) => void;\n /** A continuous edit (slider drag, colour pick) started: merge changes into one undo step. */\n onGestureStart?: () => void;\n onGestureEnd?: () => void;\n disabled?: boolean;\n /** DOM id of the main input (for a `<label htmlFor>`). */\n id?: string;\n}\n\nexport const parameterInputClass =\n 'h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50';\nexport const parameterButtonClass =\n 'inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50';\nconst rangeClass = 'h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50';\n\ntype CommitInputProps = {\n value: string;\n /** Called with the text and the element (e.g. to show the stored value again after a refused edit). */\n onCommit: (value: string, input: HTMLInputElement | HTMLTextAreaElement) => void;\n multiline?: boolean;\n} & Omit<React.InputHTMLAttributes<HTMLInputElement> & React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange' | 'defaultValue'>;\n\n/** A text box that commits on blur or Enter (Mod+Enter when multiline) and resets on Escape. */\nexport function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }: CommitInputProps) {\n const commit = (el: HTMLInputElement | HTMLTextAreaElement) => {\n if (el.value !== value) onCommit(el.value, el);\n };\n const keyDown = (e: React.KeyboardEvent<HTMLInputElement & HTMLTextAreaElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n if (e.key === 'Enter' && (!multiline || e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n commit(e.currentTarget);\n }\n if (e.key === 'Escape') e.currentTarget.value = value;\n };\n const props = {\n defaultValue: value,\n className: className ?? parameterInputClass,\n onBlur: (e: React.FocusEvent<HTMLInputElement & HTMLTextAreaElement>) => commit(e.currentTarget),\n onKeyDown: keyDown,\n ...rest,\n };\n // `key` remounts the box when the value changes from outside.\n return multiline ? (\n <textarea key={value} {...props} className={`${props.className} h-auto min-h-[3.5rem] py-1`} />\n ) : (\n <input key={value} type=\"text\" {...props} />\n );\n}\n\nconst plainNumber = (field: NumberParameterField, value: number) => formatParameterNumber({ ...field, unit: undefined }, value);\n\n/** A number box: type a value (units allowed), or step with the arrow keys (Shift ×10). */\nexport function ParameterNumberInput({\n field,\n value,\n onChange,\n disabled,\n id,\n 'aria-label': ariaLabel,\n}: ParameterControlProps<NumberParameterField, number> & { 'aria-label'?: string }) {\n return (\n <div className=\"relative min-w-0 flex-1\">\n <ParameterCommitInput\n id={id}\n inputMode=\"decimal\"\n aria-label={ariaLabel ?? parameterLabel(field)}\n value={plainNumber(field, value)}\n disabled={disabled || field.readOnly}\n className={`${parameterInputClass} ${field.unit ? 'pr-6' : ''}`}\n onCommit={(text, input) => {\n const n = parseParameterNumberInput(field, text);\n // Show the stored value again when the text was not a number or clamps to it.\n if (n === null || n === value) input.value = plainNumber(field, value);\n else onChange(n);\n }}\n onKeyDown={(e) => {\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n const step = parameterStep(field) * (e.shiftKey ? 10 : 1);\n onChange(clampParameterNumber(field, value + (e.key === 'ArrowUp' ? step : -step)));\n }}\n />\n {field.unit && (\n <span className=\"pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground\">{field.unit}</span>\n )}\n </div>\n );\n}\n\n/** A slider plus number box for `number` / `int` fields (number box only for `control: 'input'` or unbounded fields). */\nexport function ParameterSlider(props: ParameterControlProps<NumberParameterField, number>) {\n const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;\n const dragging = useRef(false);\n const end = () => {\n if (!dragging.current) return;\n dragging.current = false;\n onGestureEnd?.();\n };\n if (!isSliderField(field)) return <ParameterNumberInput {...props} />;\n const step = parameterStep(field);\n return (\n <div className=\"flex items-center gap-2\">\n <input\n type=\"range\"\n aria-label={parameterLabel(field)}\n aria-valuetext={formatParameterNumber(field, value)}\n min={field.min}\n max={field.max}\n step={step}\n value={value}\n disabled={disabled || field.readOnly}\n className={rangeClass}\n onPointerDown={() => {\n if (dragging.current) return;\n dragging.current = true;\n onGestureStart?.();\n }}\n onPointerUp={end}\n onPointerCancel={end}\n onBlur={end}\n onChange={(e) => onChange(clampParameterNumber(field, Number(e.target.value)))}\n />\n <div className=\"w-20 shrink-0\">\n <ParameterNumberInput {...props} id={props.id} aria-label={`${parameterLabel(field)} value`} />\n </div>\n </div>\n );\n}\n\n/** An on/off switch for `boolean` fields. */\nexport function ParameterToggle({ field, value, onChange, disabled, id }: ParameterControlProps<BooleanParameterField, boolean>) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n id={id}\n aria-checked={value}\n aria-label={parameterLabel(field)}\n disabled={disabled || field.readOnly}\n onClick={() => onChange(!value)}\n className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50 ${\n value ? 'bg-primary' : 'bg-muted'\n }`}\n >\n <span\n className={`pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? 'translate-x-4' : 'translate-x-0.5'}`}\n />\n </button>\n );\n}\n\n/** A dropdown (or segmented buttons with `control: 'segmented'`) for `choice` fields. */\nexport function ParameterSelect({ field, value, onChange, disabled, id }: ParameterControlProps<ChoiceParameterField, string | number>) {\n const options = getChoiceOptions(field);\n const off = disabled || field.readOnly;\n if (field.control === 'segmented') {\n return (\n <div role=\"radiogroup\" aria-label={parameterLabel(field)} className=\"inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5\">\n {options.map((o) => (\n <button\n key={String(o.value)}\n type=\"button\"\n role=\"radio\"\n aria-checked={o.value === value}\n title={o.description}\n disabled={off}\n onClick={() => onChange(o.value)}\n className={`rounded px-2 py-0.5 text-xs disabled:opacity-50 ${\n o.value === value ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {choiceOptionLabel(o)}\n </button>\n ))}\n </div>\n );\n }\n const index = options.findIndex((o) => o.value === value);\n return (\n <select\n id={id}\n aria-label={parameterLabel(field)}\n value={String(index)}\n disabled={off}\n onChange={(e) => {\n const option = options[Number(e.target.value)];\n if (option) onChange(option.value);\n }}\n className={parameterInputClass}\n >\n {options.map((o, i) => (\n <option key={String(o.value)} value={String(i)} title={o.description}>\n {choiceOptionLabel(o)}\n </option>\n ))}\n </select>\n );\n}\n\n/** A colour picker with a hex box, an alpha slider (with `alpha`) and swatches. */\nexport function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }: ParameterControlProps<ColorParameterField, string>) {\n const picking = useRef(false);\n const alpha = !!field.alpha;\n const hex = normalizeHexColor(value, true) ?? '#000000ff';\n const rgb = hex.slice(0, 7);\n const a = hex.slice(7);\n const off = disabled || field.readOnly;\n const start = () => {\n if (picking.current) return;\n picking.current = true;\n onGestureStart?.();\n };\n const end = () => {\n if (!picking.current) return;\n picking.current = false;\n onGestureEnd?.();\n };\n return (\n <div className=\"space-y-1.5\">\n <div className=\"flex items-center gap-2\">\n <span className=\"relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]\">\n <span className=\"absolute inset-0\" style={{ background: alpha ? hex : rgb }} />\n <input\n type=\"color\"\n aria-label={parameterLabel(field)}\n value={rgb}\n disabled={off}\n className=\"absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed\"\n onChange={(e) => {\n start();\n onChange(alpha ? `${e.target.value}${a}` : e.target.value);\n }}\n onBlur={end}\n />\n </span>\n <ParameterCommitInput\n id={id}\n aria-label={`${parameterLabel(field)} hex`}\n value={alpha ? hex : rgb}\n disabled={off}\n spellCheck={false}\n className={`${parameterInputClass} font-mono`}\n onCommit={(text) => {\n const next = normalizeHexColor(text, alpha);\n if (next) onChange(next);\n }}\n />\n </div>\n {alpha && (\n <input\n type=\"range\"\n aria-label={`${parameterLabel(field)} opacity`}\n min={0}\n max={255}\n step={1}\n value={parseInt(a, 16)}\n disabled={off}\n className={rangeClass}\n onPointerDown={start}\n onPointerUp={end}\n onPointerCancel={end}\n onBlur={end}\n onChange={(e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, '0')}`)}\n />\n )}\n {field.swatches && field.swatches.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {field.swatches.map((s) => {\n const swatch = normalizeHexColor(s, alpha);\n if (!swatch) return null;\n return (\n <button\n key={s}\n type=\"button\"\n title={swatch}\n aria-label={`${parameterLabel(field)} ${swatch}`}\n aria-pressed={swatch === (alpha ? hex : rgb)}\n disabled={off}\n onClick={() => onChange(swatch)}\n className=\"h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary\"\n style={{ background: swatch }}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/** One number box per component for `vector` fields. */\nexport function ParameterVector({ field, value, onChange, disabled, id }: ParameterControlProps<VectorParameterField, number[]>) {\n const size = vectorSize(field);\n return (\n <div className=\"grid gap-1.5\" style={{ gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }}>\n {Array.from({ length: size }, (_, i) => {\n const component = vectorComponentField(field, i);\n return (\n <label key={i} className=\"flex min-w-0 items-center gap-1\">\n <span className=\"w-3 shrink-0 text-[10px] uppercase text-muted-foreground\">{component.label}</span>\n <ParameterNumberInput\n field={component}\n value={value[i] ?? 0}\n id={i === 0 ? id : undefined}\n aria-label={`${parameterLabel(field)} ${component.label}`}\n disabled={disabled || field.readOnly}\n onChange={(n) => onChange(Array.from({ length: size }, (_, j) => (j === i ? n : (value[j] ?? 0))))}\n />\n </label>\n );\n })}\n </div>\n );\n}\n\nexport interface ParameterImageProps extends ParameterControlProps<ImageParameterField, string> {\n /** Turns a picked file into a URL (upload it, or `URL.createObjectURL`). Without it there is no file button. */\n resolveFile?: (file: File) => string | Promise<string>;\n}\n\n/** An image picker: suggested thumbnails, a URL box, an optional file button and a clear button. */\nexport function ParameterImage({ field, value, onChange, disabled, id, resolveFile }: ParameterImageProps) {\n const off = disabled || field.readOnly;\n const name = parameterLabel(field);\n return (\n <div className=\"space-y-1.5\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted\">\n {value ? (\n <img src={value} alt={name} className=\"h-full w-full object-cover\" />\n ) : (\n <span className=\"text-[10px] text-muted-foreground\">None</span>\n )}\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <ParameterCommitInput\n id={id}\n aria-label={`${name} URL`}\n placeholder=\"Image URL\"\n value={value.startsWith('data:') ? '' : value}\n disabled={off}\n onCommit={(text) => onChange(text.trim())}\n />\n <div className=\"flex gap-1\">\n {resolveFile && (\n <label className={`${parameterButtonClass} cursor-pointer ${off ? 'pointer-events-none opacity-50' : ''}`}>\n Choose file…\n <input\n type=\"file\"\n className=\"sr-only\"\n accept={field.accept ?? 'image/*'}\n disabled={off}\n aria-label={`${name} file`}\n onChange={async (e) => {\n const file = e.target.files?.[0];\n e.target.value = '';\n if (file) onChange(await resolveFile(file));\n }}\n />\n </label>\n )}\n {value && (\n <button type=\"button\" className={parameterButtonClass} disabled={off} onClick={() => onChange('')} aria-label={`Clear ${name}`}>\n Clear\n </button>\n )}\n </div>\n </div>\n </div>\n {field.options && field.options.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {field.options.map((o) => (\n <button\n key={o.url}\n type=\"button\"\n title={o.label ?? o.url}\n aria-label={o.label ?? o.url}\n aria-pressed={o.url === value}\n disabled={off}\n onClick={() => onChange(o.url)}\n className=\"h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary\"\n >\n <img src={o.url} alt=\"\" className=\"h-full w-full object-cover\" />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n/** A text box (or textarea with `multiline`) that commits on blur or Enter. */\nexport function ParameterText({ field, value, onChange, disabled, id }: ParameterControlProps<TextParameterField, string>) {\n return (\n <ParameterCommitInput\n id={id}\n aria-label={parameterLabel(field)}\n multiline={field.multiline}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n value={value}\n disabled={disabled || field.readOnly}\n onCommit={onChange}\n />\n );\n}\n\nexport interface KeyframeDiamondProps {\n state: ParameterKeyState;\n onToggle: () => void;\n /** The parameter's label (for the accessible name). */\n label: string;\n disabled?: boolean;\n className?: string;\n}\n\n/** A keyframe toggle: filled when a key sits at the playhead, tinted when the value is animated. */\nexport function KeyframeDiamond({ state, onToggle, label, disabled, className = '' }: KeyframeDiamondProps) {\n const keyed = state === 'keyed';\n return (\n <button\n type=\"button\"\n data-key-state={state}\n aria-pressed={keyed}\n aria-label={`${keyed ? 'Remove' : 'Add'} keyframe for ${label}`}\n title={keyed ? 'Remove keyframe here' : 'Add keyframe at the playhead'}\n disabled={disabled}\n onClick={onToggle}\n className={`inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-40 ${\n state === 'none' ? 'text-muted-foreground hover:text-foreground' : 'text-amber-500'\n } ${className}`}\n >\n <svg viewBox=\"0 0 10 10\" className=\"h-2.5 w-2.5\" aria-hidden=\"true\">\n <path\n d=\"M5 0.8 9.2 5 5 9.2 0.8 5Z\"\n fill={keyed ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n opacity={state === 'animated' ? 0.7 : 1}\n />\n </svg>\n </button>\n );\n}\n\nexport interface ParameterFieldControlProps extends ParameterControlProps<ParameterField, ParameterValue> {\n resolveFile?: ParameterImageProps['resolveFile'];\n}\n\n/** The control for any field type. */\nexport function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest }: ParameterFieldControlProps) {\n const set = onChange as (value: any) => void;\n switch (field.type) {\n case 'number':\n case 'int':\n return <ParameterSlider field={field} value={value as number} onChange={set} {...rest} />;\n case 'boolean':\n return <ParameterToggle field={field} value={value as boolean} onChange={set} {...rest} />;\n case 'choice':\n return <ParameterSelect field={field} value={value as string | number} onChange={set} {...rest} />;\n case 'color':\n return <ParameterColor field={field} value={value as string} onChange={set} {...rest} />;\n case 'vector':\n return <ParameterVector field={field} value={value as number[]} onChange={set} {...rest} />;\n case 'image':\n return <ParameterImage field={field} value={value as string} onChange={set} resolveFile={resolveFile} {...rest} />;\n case 'text':\n return <ParameterText field={field} value={value as string} onChange={set} {...rest} />;\n }\n}\n\n"],"mappings":"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","names":["import_react","import_react","import_effects","import_jsx_runtime","import_react","import_effects","import_parameters","import_react","import_jsx_runtime","_","import_jsx_runtime","React"]}
1
+ {"version":3,"sources":["../../src/effects/index.ts","../../src/effects/useEffects.ts","../../src/effects/EffectThumbnail.tsx","../../src/effects/EffectsBrowser.tsx","../../src/effects/EffectParameters.tsx","../../src/parameters/fields.tsx"],"sourcesContent":["export * from './useEffects';\nexport * from './EffectThumbnail';\nexport * from './EffectsBrowser';\nexport * from './EffectParameters';\n","import { useEffect, useState } from 'react';\nimport type { EffectDescription, EffectProvider } from '@pixerate/editor/effects';\n\n/** Anything that takes an effects provider: every editor controller with effects support. */\nexport interface EffectsHost {\n useEffects(provider: EffectProvider): () => void;\n}\n\n/**\n * Registers `provider` with an editor while mounted (or while it is the one passed), and removes it\n * again on unmount. Pass `null` or `undefined` for no provider: the editor then behaves as without\n * effects support.\n */\nexport function useEffects(host: EffectsHost | null | undefined, provider: EffectProvider | null | undefined): void {\n useEffect(() => {\n if (!host || !provider) return;\n return host.useEffects(provider);\n }, [host, provider]);\n}\n\n/** An effect's description from the provider, loaded when `effect` changes (`null` while loading or on error). */\nexport function useEffectDescription(provider: EffectProvider | null | undefined, effect: string | null | undefined): { description: EffectDescription | null; error: string | null } {\n const [state, setState] = useState<{ key: string; description: EffectDescription | null; error: string | null }>({ key: '', description: null, error: null });\n const key = provider && effect ? `${provider.id}:${effect}` : '';\n useEffect(() => {\n if (!provider || !effect) return;\n let live = true;\n Promise.resolve()\n .then(() => provider.describe(effect))\n .then(\n (description) => live && setState({ key, description, error: null }),\n (e: unknown) => live && setState({ key, description: null, error: e instanceof Error ? e.message : String(e) }),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, key]);\n return state.key === key ? { description: state.description, error: state.error } : { description: null, error: null };\n}\n","import React, { useEffect, useRef, useState } from 'react';\nimport { renderEffectThumbnail, type EffectProvider, type EffectSummary } from '@pixerate/editor/effects';\n\nexport interface EffectThumbnailProps {\n provider: EffectProvider;\n effect: EffectSummary;\n /** Pixel size of the preview (default 96 × 54). */\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A preview of an effect drawn by the provider's own renderer (filters on a sample picture,\n * transitions half way, generators mid-way through). Motion effects and failures show a label.\n */\nexport function EffectThumbnail({ provider, effect, width = 96, height = 54, className = '' }: EffectThumbnailProps) {\n const ref = useRef<HTMLCanvasElement>(null);\n const [failed, setFailed] = useState(effect.kind === 'motion');\n useEffect(() => {\n if (effect.kind === 'motion') return;\n let live = true;\n renderEffectThumbnail(provider, effect, { width, height }).then(\n (pixels) => {\n const ctx = live ? ref.current?.getContext('2d') : null;\n if (!ctx || typeof ImageData === 'undefined') return;\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), 0, 0);\n },\n () => live && setFailed(true),\n );\n return () => {\n live = false;\n };\n }, [provider, effect, width, height]);\n return (\n <div\n className={`relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`}\n style={{ aspectRatio: `${width} / ${height}` }}\n data-effect-thumbnail={effect.id}\n >\n {failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground\">{effect.kind === 'motion' ? 'Motion' : 'No preview'}</span>\n ) : (\n <canvas ref={ref} width={width} height={height} className=\"h-full w-full\" aria-hidden=\"true\" />\n )}\n </div>\n );\n}\n","import React, { useEffect, useMemo, useState } from 'react';\nimport { EFFECT_USE_LABELS, type EffectKind, type EffectProvider, type EffectSummary, type EffectUse } from '@pixerate/editor/effects';\nimport { EffectThumbnail } from './EffectThumbnail';\n\nexport interface EffectsBrowserProps {\n /** The editor's provider; without one the browser says effects are unavailable. */\n provider: EffectProvider | null | undefined;\n /** Only effects with one of these uses (default: all but motion). */\n uses?: readonly EffectUse[];\n /** Only effects of these kinds (default: any). */\n kinds?: readonly EffectKind[];\n /** Called with the effect the user picks. */\n onPick: (effect: EffectSummary) => void;\n /** Draw previews with the provider's renderer (default true). */\n thumbnails?: boolean;\n /** Heading (default \"Effects\"). */\n title?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Browses a provider's effects: a search box, a category filter (and a use filter when more than\n * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.\n */\nexport function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = 'Effects', className = '' }: EffectsBrowserProps) {\n const [all, setAll] = useState<readonly EffectSummary[]>([]);\n const [query, setQuery] = useState('');\n const [category, setCategory] = useState('');\n const [use, setUse] = useState<EffectUse | ''>('');\n const [error, setError] = useState<string | null>(null);\n useEffect(() => {\n if (!provider) return setAll([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.list())\n .then(\n (list) => live && (setAll(list), setError(null)),\n (e: unknown) => live && setError(e instanceof Error ? e.message : String(e)),\n );\n return () => {\n live = false;\n };\n }, [provider]);\n const [found, setFound] = useState<readonly EffectSummary[]>([]);\n useEffect(() => {\n if (!provider) return setFound([]);\n let live = true;\n Promise.resolve()\n .then(() => provider.search(query, { ...(category ? { category } : {}), ...(use ? { use } : {}) }))\n .then((list) => live && setFound(list), () => live && setFound([]));\n return () => {\n live = false;\n };\n }, [provider, query, category, use]);\n const allowed = (e: EffectSummary) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== 'motion' || uses.includes('motion') : e.kind !== 'motion') && (!uses || e.uses.some((u) => uses.includes(u)));\n const categories = useMemo(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);\n const shown = found.filter(allowed);\n if (!provider)\n return (\n <div className={`rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`} data-effects-browser=\"\" data-effects-unavailable=\"\">\n No effects provider is registered, so effects cannot be added. Effects already in the document are kept.\n </div>\n );\n return (\n <section className={`flex flex-col gap-2 ${className}`} data-effects-browser=\"\" aria-label=\"Effects browser\">\n {title && <h3 className=\"text-xs font-semibold\">{title}</h3>}\n <div className=\"flex flex-wrap gap-1\">\n <input\n type=\"search\"\n className=\"min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-0.5 text-xs\"\n placeholder=\"Search effects\"\n aria-label=\"Search effects\"\n value={query}\n onChange={(e) => setQuery(e.currentTarget.value)}\n />\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Category\" value={category} onChange={(e) => setCategory(e.currentTarget.value)}>\n <option value=\"\">All categories</option>\n {categories.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n {uses && uses.length > 1 && (\n <select className=\"rounded border border-border bg-transparent px-1 py-0.5 text-xs\" aria-label=\"Use\" value={use} onChange={(e) => setUse(e.currentTarget.value as EffectUse | '')}>\n <option value=\"\">Any use</option>\n {uses.map((u) => (\n <option key={u} value={u}>\n {EFFECT_USE_LABELS[u]}\n </option>\n ))}\n </select>\n )}\n </div>\n {error && <p role=\"alert\" className=\"text-[11px] text-destructive\">{error}</p>}\n <ul className=\"grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto\" role=\"list\">\n {shown.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className=\"flex w-full flex-col gap-1 rounded border border-transparent p-1 text-left hover:border-border focus-visible:border-border\"\n title={e.description ?? e.name}\n data-effect-id={e.id}\n onClick={() => onPick(e)}\n >\n {thumbnails && <EffectThumbnail provider={provider} effect={e} />}\n <span className=\"truncate text-[11px] font-medium\">{e.name}</span>\n <span className=\"truncate text-[10px] text-muted-foreground\">{e.uses.map((u) => EFFECT_USE_LABELS[u]).join(' · ')}</span>\n </button>\n </li>\n ))}\n </ul>\n {!shown.length && !error && <p className=\"text-[11px] text-muted-foreground\">No effects match.</p>}\n </section>\n );\n}\n","import React, { useId, useMemo } from 'react';\nimport { effectFieldValue, effectParameterFields, type EffectDescription } from '@pixerate/editor/effects';\nimport { parameterLabel, parameterValuesEqual, type ParameterValue } from '@pixerate/editor/parameters';\nimport { ParameterFieldControl } from '../parameters/fields';\n\nexport interface EffectParametersProps {\n /** The effect's description (`describe`). */\n description: EffectDescription;\n /** Its saved values; controls left out show their defaults. */\n values: Readonly<Record<string, unknown>>;\n /**\n * A control changed. While a slider or colour drag is under way `gesture` is a key unique to\n * that drag, so the host can fold its changes into one undo step; otherwise `null`.\n */\n onChange: (control: string, value: ParameterValue, gesture: string | null) => void;\n /** Resets one control to its default. */\n onReset?: (control: string) => void;\n /** Controls that are keyframed: shown read-only with a note (edit them as keyframes). */\n keyframed?: ReadonlySet<string>;\n /** Applies a preset's values. */\n onPreset?: (values: Readonly<Record<string, unknown>>) => void;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The parameter panel of an effect, generated from its JSON Schema controls: sliders for bounded\n * numbers, switches, choices, colour pickers, vectors and text, with each control's description,\n * per-control reset and the effect's presets.\n */\nexport function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = '' }: EffectParametersProps) {\n const fields = useMemo(() => effectParameterFields(description), [description]);\n const idBase = useId();\n const gesture = React.useRef<string | null>(null);\n const count = React.useRef(0);\n return (\n <div className={`space-y-2 ${className}`} data-effect-parameters={description.id}>\n {onPreset && description.presets.length > 0 && (\n <select\n className=\"w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs\"\n aria-label={`${description.name} presets`}\n value=\"\"\n disabled={disabled}\n onChange={(e) => {\n const preset = description.presets.find((p) => p.name === e.currentTarget.value);\n if (preset) onPreset({ ...description.defaults, ...preset.values });\n }}\n >\n <option value=\"\">Presets…</option>\n {description.presets.map((p) => (\n <option key={p.name} value={p.name}>\n {p.name}\n </option>\n ))}\n </select>\n )}\n {fields.map((field) => {\n const label = parameterLabel(field);\n const inputId = `${idBase}-${field.id}`;\n const keyed = keyframed?.has(field.id) ?? false;\n const value = effectFieldValue(field, values[field.id]);\n const changed = !keyed && !parameterValuesEqual(value, field.default);\n return (\n <div key={field.id} className=\"space-y-1\" data-effect-control={field.id}>\n <div className=\"flex items-center gap-1.5\">\n <label htmlFor={inputId} className=\"min-w-0 flex-1 truncate text-xs font-medium\" title={field.description}>\n {label}\n </label>\n {keyed && <span className=\"text-[10px] text-muted-foreground\">keyframed</span>}\n {onReset && changed && !disabled && (\n <button type=\"button\" className=\"rounded px-1 text-[11px] text-muted-foreground hover:text-foreground\" aria-label={`Reset ${label}`} onClick={() => onReset(field.id)}>\n ↺\n </button>\n )}\n </div>\n <ParameterFieldControl\n field={field}\n value={value}\n id={inputId}\n disabled={disabled || keyed}\n onChange={(v) => onChange(field.id, v, gesture.current)}\n onGestureStart={() => (gesture.current = `${idBase}:${field.id}:${++count.current}`)}\n onGestureEnd={() => (gesture.current = null)}\n />\n </div>\n );\n })}\n {!fields.length && <p className=\"text-[11px] text-muted-foreground\">No controls.</p>}\n </div>\n );\n}\n","import React, { useRef } from 'react';\nimport {\n choiceOptionLabel,\n formatParameterNumber,\n getChoiceOptions,\n isSliderField,\n normalizeHexColor,\n parameterLabel,\n parameterStep,\n parseParameterNumberInput,\n clampParameterNumber,\n vectorComponentField,\n vectorSize,\n type BooleanParameterField,\n type ChoiceParameterField,\n type ColorParameterField,\n type ImageParameterField,\n type NumberParameterField,\n type ParameterField,\n type ParameterKeyState,\n type ParameterValue,\n type TextParameterField,\n type VectorParameterField,\n} from '@pixerate/editor/parameters';\n\n/** Props every field control takes. Controls are controller-free: wire them to anything. */\nexport interface ParameterControlProps<F extends ParameterField, V extends ParameterValue> {\n field: F;\n value: V;\n onChange: (value: V) => void;\n /** A continuous edit (slider drag, colour pick) started: merge changes into one undo step. */\n onGestureStart?: () => void;\n onGestureEnd?: () => void;\n disabled?: boolean;\n /** DOM id of the main input (for a `<label htmlFor>`). */\n id?: string;\n}\n\nexport const parameterInputClass =\n 'h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50';\nexport const parameterButtonClass =\n 'inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50';\nconst rangeClass = 'h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50';\n\ntype CommitInputProps = {\n value: string;\n /** Called with the text and the element (e.g. to show the stored value again after a refused edit). */\n onCommit: (value: string, input: HTMLInputElement | HTMLTextAreaElement) => void;\n multiline?: boolean;\n} & Omit<React.InputHTMLAttributes<HTMLInputElement> & React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange' | 'defaultValue'>;\n\n/** A text box that commits on blur or Enter (Mod+Enter when multiline) and resets on Escape. */\nexport function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown, ...rest }: CommitInputProps) {\n const commit = (el: HTMLInputElement | HTMLTextAreaElement) => {\n if (el.value !== value) onCommit(el.value, el);\n };\n const keyDown = (e: React.KeyboardEvent<HTMLInputElement & HTMLTextAreaElement>) => {\n onKeyDown?.(e);\n if (e.defaultPrevented) return;\n if (e.key === 'Enter' && (!multiline || e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n commit(e.currentTarget);\n }\n if (e.key === 'Escape') e.currentTarget.value = value;\n };\n const props = {\n defaultValue: value,\n className: className ?? parameterInputClass,\n onBlur: (e: React.FocusEvent<HTMLInputElement & HTMLTextAreaElement>) => commit(e.currentTarget),\n onKeyDown: keyDown,\n ...rest,\n };\n // `key` remounts the box when the value changes from outside.\n return multiline ? (\n <textarea key={value} {...props} className={`${props.className} h-auto min-h-[3.5rem] py-1`} />\n ) : (\n <input key={value} type=\"text\" {...props} />\n );\n}\n\nconst plainNumber = (field: NumberParameterField, value: number) => formatParameterNumber({ ...field, unit: undefined }, value);\n\n/** A number box: type a value (units allowed), or step with the arrow keys (Shift ×10). */\nexport function ParameterNumberInput({\n field,\n value,\n onChange,\n disabled,\n id,\n 'aria-label': ariaLabel,\n}: ParameterControlProps<NumberParameterField, number> & { 'aria-label'?: string }) {\n return (\n <div className=\"relative min-w-0 flex-1\">\n <ParameterCommitInput\n id={id}\n inputMode=\"decimal\"\n aria-label={ariaLabel ?? parameterLabel(field)}\n value={plainNumber(field, value)}\n disabled={disabled || field.readOnly}\n className={`${parameterInputClass} ${field.unit ? 'pr-6' : ''}`}\n onCommit={(text, input) => {\n const n = parseParameterNumberInput(field, text);\n // Show the stored value again when the text was not a number or clamps to it.\n if (n === null || n === value) input.value = plainNumber(field, value);\n else onChange(n);\n }}\n onKeyDown={(e) => {\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;\n e.preventDefault();\n const step = parameterStep(field) * (e.shiftKey ? 10 : 1);\n onChange(clampParameterNumber(field, value + (e.key === 'ArrowUp' ? step : -step)));\n }}\n />\n {field.unit && (\n <span className=\"pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground\">{field.unit}</span>\n )}\n </div>\n );\n}\n\n/** A slider plus number box for `number` / `int` fields (number box only for `control: 'input'` or unbounded fields). */\nexport function ParameterSlider(props: ParameterControlProps<NumberParameterField, number>) {\n const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;\n const dragging = useRef(false);\n const end = () => {\n if (!dragging.current) return;\n dragging.current = false;\n onGestureEnd?.();\n };\n if (!isSliderField(field)) return <ParameterNumberInput {...props} />;\n const step = parameterStep(field);\n return (\n <div className=\"flex items-center gap-2\">\n <input\n type=\"range\"\n aria-label={parameterLabel(field)}\n aria-valuetext={formatParameterNumber(field, value)}\n min={field.min}\n max={field.max}\n step={step}\n value={value}\n disabled={disabled || field.readOnly}\n className={rangeClass}\n onPointerDown={() => {\n if (dragging.current) return;\n dragging.current = true;\n onGestureStart?.();\n }}\n onPointerUp={end}\n onPointerCancel={end}\n onBlur={end}\n onChange={(e) => onChange(clampParameterNumber(field, Number(e.target.value)))}\n />\n <div className=\"w-20 shrink-0\">\n <ParameterNumberInput {...props} id={props.id} aria-label={`${parameterLabel(field)} value`} />\n </div>\n </div>\n );\n}\n\n/** An on/off switch for `boolean` fields. */\nexport function ParameterToggle({ field, value, onChange, disabled, id }: ParameterControlProps<BooleanParameterField, boolean>) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n id={id}\n aria-checked={value}\n aria-label={parameterLabel(field)}\n disabled={disabled || field.readOnly}\n onClick={() => onChange(!value)}\n className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50 ${\n value ? 'bg-primary' : 'bg-muted'\n }`}\n >\n <span\n className={`pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? 'translate-x-4' : 'translate-x-0.5'}`}\n />\n </button>\n );\n}\n\n/** A dropdown (or segmented buttons with `control: 'segmented'`) for `choice` fields. */\nexport function ParameterSelect({ field, value, onChange, disabled, id }: ParameterControlProps<ChoiceParameterField, string | number>) {\n const options = getChoiceOptions(field);\n const off = disabled || field.readOnly;\n if (field.control === 'segmented') {\n return (\n <div role=\"radiogroup\" aria-label={parameterLabel(field)} className=\"inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5\">\n {options.map((o) => (\n <button\n key={String(o.value)}\n type=\"button\"\n role=\"radio\"\n aria-checked={o.value === value}\n title={o.description}\n disabled={off}\n onClick={() => onChange(o.value)}\n className={`rounded px-2 py-0.5 text-xs disabled:opacity-50 ${\n o.value === value ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {choiceOptionLabel(o)}\n </button>\n ))}\n </div>\n );\n }\n const index = options.findIndex((o) => o.value === value);\n return (\n <select\n id={id}\n aria-label={parameterLabel(field)}\n value={String(index)}\n disabled={off}\n onChange={(e) => {\n const option = options[Number(e.target.value)];\n if (option) onChange(option.value);\n }}\n className={parameterInputClass}\n >\n {options.map((o, i) => (\n <option key={String(o.value)} value={String(i)} title={o.description}>\n {choiceOptionLabel(o)}\n </option>\n ))}\n </select>\n );\n}\n\n/** A colour picker with a hex box, an alpha slider (with `alpha`) and swatches. */\nexport function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }: ParameterControlProps<ColorParameterField, string>) {\n const picking = useRef(false);\n const alpha = !!field.alpha;\n const hex = normalizeHexColor(value, true) ?? '#000000ff';\n const rgb = hex.slice(0, 7);\n const a = hex.slice(7);\n const off = disabled || field.readOnly;\n const start = () => {\n if (picking.current) return;\n picking.current = true;\n onGestureStart?.();\n };\n const end = () => {\n if (!picking.current) return;\n picking.current = false;\n onGestureEnd?.();\n };\n return (\n <div className=\"space-y-1.5\">\n <div className=\"flex items-center gap-2\">\n <span className=\"relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]\">\n <span className=\"absolute inset-0\" style={{ background: alpha ? hex : rgb }} />\n <input\n type=\"color\"\n aria-label={parameterLabel(field)}\n value={rgb}\n disabled={off}\n className=\"absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed\"\n onChange={(e) => {\n start();\n onChange(alpha ? `${e.target.value}${a}` : e.target.value);\n }}\n onBlur={end}\n />\n </span>\n <ParameterCommitInput\n id={id}\n aria-label={`${parameterLabel(field)} hex`}\n value={alpha ? hex : rgb}\n disabled={off}\n spellCheck={false}\n className={`${parameterInputClass} font-mono`}\n onCommit={(text) => {\n const next = normalizeHexColor(text, alpha);\n if (next) onChange(next);\n }}\n />\n </div>\n {alpha && (\n <input\n type=\"range\"\n aria-label={`${parameterLabel(field)} opacity`}\n min={0}\n max={255}\n step={1}\n value={parseInt(a, 16)}\n disabled={off}\n className={rangeClass}\n onPointerDown={start}\n onPointerUp={end}\n onPointerCancel={end}\n onBlur={end}\n onChange={(e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, '0')}`)}\n />\n )}\n {field.swatches && field.swatches.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {field.swatches.map((s) => {\n const swatch = normalizeHexColor(s, alpha);\n if (!swatch) return null;\n return (\n <button\n key={s}\n type=\"button\"\n title={swatch}\n aria-label={`${parameterLabel(field)} ${swatch}`}\n aria-pressed={swatch === (alpha ? hex : rgb)}\n disabled={off}\n onClick={() => onChange(swatch)}\n className=\"h-5 w-5 rounded border border-input aria-pressed:ring-1 aria-pressed:ring-primary\"\n style={{ background: swatch }}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\n/** One number box per component for `vector` fields. */\nexport function ParameterVector({ field, value, onChange, disabled, id }: ParameterControlProps<VectorParameterField, number[]>) {\n const size = vectorSize(field);\n return (\n <div className=\"grid gap-1.5\" style={{ gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }}>\n {Array.from({ length: size }, (_, i) => {\n const component = vectorComponentField(field, i);\n return (\n <label key={i} className=\"flex min-w-0 items-center gap-1\">\n <span className=\"w-3 shrink-0 text-[10px] uppercase text-muted-foreground\">{component.label}</span>\n <ParameterNumberInput\n field={component}\n value={value[i] ?? 0}\n id={i === 0 ? id : undefined}\n aria-label={`${parameterLabel(field)} ${component.label}`}\n disabled={disabled || field.readOnly}\n onChange={(n) => onChange(Array.from({ length: size }, (_, j) => (j === i ? n : (value[j] ?? 0))))}\n />\n </label>\n );\n })}\n </div>\n );\n}\n\nexport interface ParameterImageProps extends ParameterControlProps<ImageParameterField, string> {\n /** Turns a picked file into a URL (upload it, or `URL.createObjectURL`). Without it there is no file button. */\n resolveFile?: (file: File) => string | Promise<string>;\n}\n\n/** An image picker: suggested thumbnails, a URL box, an optional file button and a clear button. */\nexport function ParameterImage({ field, value, onChange, disabled, id, resolveFile }: ParameterImageProps) {\n const off = disabled || field.readOnly;\n const name = parameterLabel(field);\n return (\n <div className=\"space-y-1.5\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted\">\n {value ? (\n <img src={value} alt={name} className=\"h-full w-full object-cover\" />\n ) : (\n <span className=\"text-[10px] text-muted-foreground\">None</span>\n )}\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <ParameterCommitInput\n id={id}\n aria-label={`${name} URL`}\n placeholder=\"Image URL\"\n value={value.startsWith('data:') ? '' : value}\n disabled={off}\n onCommit={(text) => onChange(text.trim())}\n />\n <div className=\"flex gap-1\">\n {resolveFile && (\n <label className={`${parameterButtonClass} cursor-pointer ${off ? 'pointer-events-none opacity-50' : ''}`}>\n Choose file…\n <input\n type=\"file\"\n className=\"sr-only\"\n accept={field.accept ?? 'image/*'}\n disabled={off}\n aria-label={`${name} file`}\n onChange={async (e) => {\n const file = e.target.files?.[0];\n e.target.value = '';\n if (file) onChange(await resolveFile(file));\n }}\n />\n </label>\n )}\n {value && (\n <button type=\"button\" className={parameterButtonClass} disabled={off} onClick={() => onChange('')} aria-label={`Clear ${name}`}>\n Clear\n </button>\n )}\n </div>\n </div>\n </div>\n {field.options && field.options.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {field.options.map((o) => (\n <button\n key={o.url}\n type=\"button\"\n title={o.label ?? o.url}\n aria-label={o.label ?? o.url}\n aria-pressed={o.url === value}\n disabled={off}\n onClick={() => onChange(o.url)}\n className=\"h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary\"\n >\n <img src={o.url} alt=\"\" className=\"h-full w-full object-cover\" />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n/** A text box (or textarea with `multiline`) that commits on blur or Enter. */\nexport function ParameterText({ field, value, onChange, disabled, id }: ParameterControlProps<TextParameterField, string>) {\n return (\n <ParameterCommitInput\n id={id}\n aria-label={parameterLabel(field)}\n multiline={field.multiline}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n value={value}\n disabled={disabled || field.readOnly}\n onCommit={onChange}\n />\n );\n}\n\nexport interface KeyframeDiamondProps {\n state: ParameterKeyState;\n onToggle: () => void;\n /** The parameter's label (for the accessible name). */\n label: string;\n disabled?: boolean;\n className?: string;\n}\n\n/** A keyframe toggle: filled when a key sits at the playhead, tinted when the value is animated. */\nexport function KeyframeDiamond({ state, onToggle, label, disabled, className = '' }: KeyframeDiamondProps) {\n const keyed = state === 'keyed';\n return (\n <button\n type=\"button\"\n data-key-state={state}\n aria-pressed={keyed}\n aria-label={`${keyed ? 'Remove' : 'Add'} keyframe for ${label}`}\n title={keyed ? 'Remove keyframe here' : 'Add keyframe at the playhead'}\n disabled={disabled}\n onClick={onToggle}\n className={`inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-40 ${\n state === 'none' ? 'text-muted-foreground hover:text-foreground' : 'text-amber-500'\n } ${className}`}\n >\n <svg viewBox=\"0 0 10 10\" className=\"h-2.5 w-2.5\" aria-hidden=\"true\">\n <path\n d=\"M5 0.8 9.2 5 5 9.2 0.8 5Z\"\n fill={keyed ? 'currentColor' : 'none'}\n stroke=\"currentColor\"\n strokeWidth=\"1.3\"\n opacity={state === 'animated' ? 0.7 : 1}\n />\n </svg>\n </button>\n );\n}\n\nexport interface ParameterFieldControlProps extends ParameterControlProps<ParameterField, ParameterValue> {\n resolveFile?: ParameterImageProps['resolveFile'];\n}\n\n/** The control for any field type. */\nexport function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest }: ParameterFieldControlProps) {\n const set = onChange as (value: any) => void;\n switch (field.type) {\n case 'number':\n case 'int':\n return <ParameterSlider field={field} value={value as number} onChange={set} {...rest} />;\n case 'boolean':\n return <ParameterToggle field={field} value={value as boolean} onChange={set} {...rest} />;\n case 'choice':\n return <ParameterSelect field={field} value={value as string | number} onChange={set} {...rest} />;\n case 'color':\n return <ParameterColor field={field} value={value as string} onChange={set} {...rest} />;\n case 'vector':\n return <ParameterVector field={field} value={value as number[]} onChange={set} {...rest} />;\n case 'image':\n return <ParameterImage field={field} value={value as string} onChange={set} resolveFile={resolveFile} {...rest} />;\n case 'text':\n return <ParameterText field={field} value={value as string} onChange={set} {...rest} />;\n }\n}\n\n"],"mappings":"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","names":["import_react","import_react","import_effects","import_jsx_runtime","import_react","import_effects","import_parameters","import_react","import_jsx_runtime","_","import_jsx_runtime","React"]}
@@ -1,4 +1,4 @@
1
- import { EffectProvider, EffectDescription, EffectSummary, EffectUse } from '@pixerate/editor/effects';
1
+ import { EffectProvider, EffectDescription, EffectSummary, EffectUse, EffectKind } from '@pixerate/editor/effects';
2
2
  import React__default from 'react';
3
3
  import { ParameterValue } from '@pixerate/editor/parameters';
4
4
 
@@ -37,6 +37,8 @@ interface EffectsBrowserProps {
37
37
  provider: EffectProvider | null | undefined;
38
38
  /** Only effects with one of these uses (default: all but motion). */
39
39
  uses?: readonly EffectUse[];
40
+ /** Only effects of these kinds (default: any). */
41
+ kinds?: readonly EffectKind[];
40
42
  /** Called with the effect the user picks. */
41
43
  onPick: (effect: EffectSummary) => void;
42
44
  /** Draw previews with the provider's renderer (default true). */
@@ -49,7 +51,7 @@ interface EffectsBrowserProps {
49
51
  * Browses a provider's effects: a search box, a category filter (and a use filter when more than
50
52
  * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.
51
53
  */
52
- declare function EffectsBrowser({ provider, uses, onPick, thumbnails, title, className }: EffectsBrowserProps): React__default.JSX.Element;
54
+ declare function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails, title, className }: EffectsBrowserProps): React__default.JSX.Element;
53
55
 
54
56
  interface EffectParametersProps {
55
57
  /** The effect's description (`describe`). */
@@ -1,4 +1,4 @@
1
- import { EffectProvider, EffectDescription, EffectSummary, EffectUse } from '@pixerate/editor/effects';
1
+ import { EffectProvider, EffectDescription, EffectSummary, EffectUse, EffectKind } from '@pixerate/editor/effects';
2
2
  import React__default from 'react';
3
3
  import { ParameterValue } from '@pixerate/editor/parameters';
4
4
 
@@ -37,6 +37,8 @@ interface EffectsBrowserProps {
37
37
  provider: EffectProvider | null | undefined;
38
38
  /** Only effects with one of these uses (default: all but motion). */
39
39
  uses?: readonly EffectUse[];
40
+ /** Only effects of these kinds (default: any). */
41
+ kinds?: readonly EffectKind[];
40
42
  /** Called with the effect the user picks. */
41
43
  onPick: (effect: EffectSummary) => void;
42
44
  /** Draw previews with the provider's renderer (default true). */
@@ -49,7 +51,7 @@ interface EffectsBrowserProps {
49
51
  * Browses a provider's effects: a search box, a category filter (and a use filter when more than
50
52
  * one use is allowed), and a grid of effects with previews. Picking one calls `onPick`.
51
53
  */
52
- declare function EffectsBrowser({ provider, uses, onPick, thumbnails, title, className }: EffectsBrowserProps): React__default.JSX.Element;
54
+ declare function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails, title, className }: EffectsBrowserProps): React__default.JSX.Element;
53
55
 
54
56
  interface EffectParametersProps {
55
57
  /** The effect's description (`describe`). */
@@ -4,7 +4,7 @@ import {
4
4
  EffectsBrowser,
5
5
  useEffectDescription,
6
6
  useEffects
7
- } from "../chunk-7RMNM3KC.js";
7
+ } from "../chunk-WOQ3H7KA.js";
8
8
  import "../chunk-ZQZAKUUM.js";
9
9
  export {
10
10
  EffectParameters,
@@ -1334,7 +1334,7 @@ function EffectThumbnail({ provider, effect, width = 96, height = 54, className
1334
1334
  var import_react9 = require("react");
1335
1335
  var import_effects2 = require("@pixerate/editor/effects");
1336
1336
  var import_jsx_runtime8 = require("react/jsx-runtime");
1337
- function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Effects", className = "" }) {
1337
+ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = "Effects", className = "" }) {
1338
1338
  const [all, setAll] = (0, import_react9.useState)([]);
1339
1339
  const [query, setQuery] = (0, import_react9.useState)("");
1340
1340
  const [category, setCategory] = (0, import_react9.useState)("");
@@ -1360,8 +1360,8 @@ function EffectsBrowser({ provider, uses, onPick, thumbnails = true, title = "Ef
1360
1360
  live = false;
1361
1361
  };
1362
1362
  }, [provider, query, category, use]);
1363
- const allowed = (e) => (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
1364
- const categories = (0, import_react9.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses]);
1363
+ const allowed = (e) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
1364
+ const categories = (0, import_react9.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
1365
1365
  const shown = found.filter(allowed);
1366
1366
  if (!provider)
1367
1367
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });