@pixerate/editor 0.172.0 → 0.174.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.
Files changed (37) hide show
  1. package/dist/animation-editor/index.cjs +9 -9
  2. package/dist/animation-editor/index.js +1 -1
  3. package/dist/canvas/index.cjs +7 -7
  4. package/dist/canvas/index.js +1 -1
  5. package/dist/{chunk-CYKWYP6D.cjs → chunk-6DUEV36F.cjs} +14 -14
  6. package/dist/{chunk-7KWJEYIK.js → chunk-6G2NLMVK.js} +36 -4
  7. package/dist/{chunk-RUZQMYXH.cjs → chunk-A3A5TI6R.cjs} +43 -11
  8. package/dist/chunk-NZ3CTY7K.js +260 -0
  9. package/dist/{chunk-YN4AYYBJ.js → chunk-OZSA5YEJ.js} +1 -1
  10. package/dist/chunk-VCL7CB56.cjs +260 -0
  11. package/dist/{chunk-HCC2LBCW.js → chunk-WMVEYSOA.js} +1 -1
  12. package/dist/{chunk-3OSXHLIT.cjs → chunk-Z64NRDEH.cjs} +7 -7
  13. package/dist/commands/format.d.cts +43 -0
  14. package/dist/commands/format.d.ts +43 -0
  15. package/dist/commands/index.cjs +7 -1
  16. package/dist/commands/index.d.cts +1 -0
  17. package/dist/commands/index.d.ts +1 -0
  18. package/dist/commands/index.js +6 -0
  19. package/dist/effects/helpers.d.cts +11 -2
  20. package/dist/effects/helpers.d.ts +11 -2
  21. package/dist/effects/index.cjs +40 -34
  22. package/dist/effects/index.js +8 -2
  23. package/dist/effects/types.d.cts +2 -0
  24. package/dist/effects/types.d.ts +2 -0
  25. package/dist/image-editor/index.cjs +13 -13
  26. package/dist/image-editor/index.js +1 -1
  27. package/dist/index.cjs +7 -1
  28. package/dist/index.d.cts +1 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +6 -0
  31. package/dist/paint-editor/index.cjs +7 -7
  32. package/dist/paint-editor/index.js +2 -2
  33. package/dist/vector/index.cjs +6 -6
  34. package/dist/vector/index.js +1 -1
  35. package/dist/video-editor/index.cjs +309 -309
  36. package/dist/video-editor/index.js +3 -3
  37. package/package.json +4 -1
@@ -48,7 +48,7 @@ require('../chunk-NM3G4VQT.cjs');
48
48
 
49
49
 
50
50
 
51
- var _chunkRUZQMYXHcjs = require('../chunk-RUZQMYXH.cjs');
51
+ var _chunkA3A5TI6Rcjs = require('../chunk-A3A5TI6R.cjs');
52
52
  require('../chunk-WOFXZ3XU.cjs');
53
53
  require('../chunk-EBDCRTZI.cjs');
54
54
  require('../chunk-YQGCPL5X.cjs');
@@ -3827,7 +3827,7 @@ var AnimationEditorController = (_class = class {
3827
3827
 
3828
3828
 
3829
3829
  /** The effects provider slot (see `setEffectsProvider`). */
3830
- __init() {this.effects = new (0, _chunkRUZQMYXHcjs.EffectsSlot)()}
3830
+ __init() {this.effects = new (0, _chunkA3A5TI6Rcjs.EffectsSlot)()}
3831
3831
 
3832
3832
 
3833
3833
  __init2() {this.selection = null}
@@ -4012,7 +4012,7 @@ var AnimationEditorController = (_class = class {
4012
4012
  try {
4013
4013
  plan = await work();
4014
4014
  } catch (error) {
4015
- if (_chunkRUZQMYXHcjs.isEffectError.call(void 0, error) || error instanceof Error) return this.refuse(error.message);
4015
+ if (_chunkA3A5TI6Rcjs.isEffectError.call(void 0, error) || error instanceof Error) return this.refuse(error.message);
4016
4016
  throw error;
4017
4017
  }
4018
4018
  const r = this.apply({ op: "replace_scene", scene: plan.scene }, plan.label, { merge });
@@ -4023,11 +4023,11 @@ var AnimationEditorController = (_class = class {
4023
4023
  }
4024
4024
  async describeFor(effect, kinds) {
4025
4025
  const provider = this.effects.require();
4026
- if (!provider.applyVectorEffect) throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`${provider.name} has no vector or motion effects.`);
4026
+ if (!provider.applyVectorEffect) throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`${provider.name} has no vector or motion effects.`);
4027
4027
  const d = await provider.describe(effect);
4028
4028
  if (!kinds.includes(d.kind)) {
4029
4029
  const hint = d.kind === "motion" ? "apply it to nodes with apply_effect" : d.kind === "vector" ? "add it with add_vector_effect" : "it draws pixels, which scenes cannot hold";
4030
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.kind} effect: ${hint}.`, d.id);
4030
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.kind} effect: ${hint}.`, d.id);
4031
4031
  }
4032
4032
  return d;
4033
4033
  }
@@ -4048,7 +4048,7 @@ var AnimationEditorController = (_class = class {
4048
4048
  return this.effectEdit(async () => {
4049
4049
  this.knownNodes(targets);
4050
4050
  const d = await this.describeFor(effect, ["motion"]);
4051
- const values = _chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, d, options.values);
4051
+ const values = _chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, d, options.values);
4052
4052
  const instance = _nullishCoalesce(options.instance, () => ( nextMotionEffectInstance(this.scene)));
4053
4053
  const motion = await this.effects.require().applyVectorEffect(d.id, { svg: motionArtworkSvg(this.scene.artwork), nodeIds: [...targets] }, values);
4054
4054
  const source = { instance, effect: d.id, provider: this.effects.require().id, name: d.name, values };
@@ -4070,7 +4070,7 @@ var AnimationEditorController = (_class = class {
4070
4070
  addVectorEffect(effect, options = {}) {
4071
4071
  return this.effectEdit(async () => {
4072
4072
  const d = await this.describeFor(effect, ["vector"]);
4073
- const values = _chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, d, options.values);
4073
+ const values = _chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, d, options.values);
4074
4074
  const instance = _nullishCoalesce(options.instance, () => ( nextMotionEffectInstance(this.scene)));
4075
4075
  const motion = await this.effects.require().applyVectorEffect(d.id, motionArtworkSvg(this.scene.artwork), values);
4076
4076
  const source = { instance, effect: d.id, provider: this.effects.require().id, name: d.name, values };
@@ -4094,7 +4094,7 @@ var AnimationEditorController = (_class = class {
4094
4094
  else if (k in d.controls.properties) base[k] = d.defaults[k];
4095
4095
  else throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
4096
4096
  }
4097
- const merged = { ...base, ..._chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
4097
+ const merged = { ...base, ..._chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
4098
4098
  const { scene: bare, place } = withoutMotionEffect(this.scene, instance);
4099
4099
  const provider = this.effects.require();
4100
4100
  const source = { instance, effect: d.id, provider: provider.id, name: d.name, values: merged };
@@ -5867,7 +5867,7 @@ function createAnimationEffectsToolProvider(controller, options = {}) {
5867
5867
  if (!r.ok) throw new Error(r.error);
5868
5868
  return { target, effect: info(r.effect), effects: controller.appliedEffects().filter((e) => e.targets.includes(target)).map(info), ...r.notes.length ? { notes: r.notes } : {} };
5869
5869
  };
5870
- return withAnimationPlayback(_chunkRUZQMYXHcjs.createEffectToolProvider.call(void 0, {
5870
+ return withAnimationPlayback(_chunkA3A5TI6Rcjs.createEffectToolProvider.call(void 0, {
5871
5871
  id: _nullishCoalesce(options.id, () => ( "animation-effects")),
5872
5872
  namespace: options.namespace,
5873
5873
  title: "Animation effects",
@@ -48,7 +48,7 @@ import {
48
48
  createEffectToolProvider,
49
49
  isEffectError,
50
50
  resolveEffectValues
51
- } from "../chunk-7KWJEYIK.js";
51
+ } from "../chunk-6G2NLMVK.js";
52
52
  import "../chunk-V2ZCJRDM.js";
53
53
  import "../chunk-2N2QPO3H.js";
54
54
  import "../chunk-ODBT55Q7.js";
@@ -209,7 +209,7 @@ var _chunkNM3G4VQTcjs = require('../chunk-NM3G4VQT.cjs');
209
209
 
210
210
 
211
211
 
212
- var _chunkRUZQMYXHcjs = require('../chunk-RUZQMYXH.cjs');
212
+ var _chunkA3A5TI6Rcjs = require('../chunk-A3A5TI6R.cjs');
213
213
  require('../chunk-WOFXZ3XU.cjs');
214
214
 
215
215
 
@@ -11396,14 +11396,14 @@ function canvasNodeEffect(node) {
11396
11396
  return _chunkNM3G4VQTcjs.isShapeDescription.call(void 0, shape) ? shape.effect : void 0;
11397
11397
  }
11398
11398
  function createCanvasEffectsController(options) {
11399
- const slot = new (0, _chunkRUZQMYXHcjs.EffectsSlot)();
11399
+ const slot = new (0, _chunkA3A5TI6Rcjs.EffectsSlot)();
11400
11400
  if (options.effects) slot.set(options.effects);
11401
11401
  const describe = async (effect, place2) => {
11402
11402
  const d = await slot.require().describe(effect);
11403
11403
  const allowed = CANVAS_EFFECT_PLACES[place2];
11404
11404
  if (d.kind === "motion" || !d.uses.some((u) => allowed.includes(u))) {
11405
11405
  const where = place2 === "transition" ? "a scene transition" : place2 === "fill" ? "a fill" : "an overlay";
11406
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be ${where}, which takes ${allowed.join(" or ")} effects; search_effects with use "${allowed[0]}" lists ones that fit.`, d.id);
11406
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(`"${d.id}" (${d.name}) is a ${d.uses.join(" / ")} effect and cannot be ${where}, which takes ${allowed.join(" or ")} effects; search_effects with use "${allowed[0]}" lists ones that fit.`, d.id);
11407
11407
  }
11408
11408
  return d;
11409
11409
  };
@@ -11434,7 +11434,7 @@ function createCanvasEffectsController(options) {
11434
11434
  else if (k in d.controls.properties) base[k] = d.defaults[k];
11435
11435
  else throw new Error(`"${k}" is not a control of "${d.id}"; its controls are ${Object.keys(d.controls.properties).join(", ")}.`);
11436
11436
  }
11437
- return { ...base, ..._chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
11437
+ return { ...base, ..._chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, { ...d, defaults: base }, given) };
11438
11438
  };
11439
11439
  return {
11440
11440
  /** The provider slot. */
@@ -11464,7 +11464,7 @@ function createCanvasEffectsController(options) {
11464
11464
  const mode = _nullishCoalesce(opts.mode, () => ( "fill"));
11465
11465
  const d = await describe(effect, mode);
11466
11466
  if (opts.opacity !== void 0 && !(opts.opacity >= 0 && opts.opacity <= 1)) throw new Error("opacity must be from 0 to 1.");
11467
- const next = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, d, opts.values), mode, ...opts.opacity !== void 0 && opts.opacity !== 1 ? { opacity: opts.opacity } : {} };
11467
+ const next = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, d, opts.values), mode, ...opts.opacity !== void 0 && opts.opacity !== 1 ? { opacity: opts.opacity } : {} };
11468
11468
  commit(`Add effect ${d.name}`, withEffect(nodeId, next));
11469
11469
  return next;
11470
11470
  },
@@ -11521,7 +11521,7 @@ function createCanvasEffectsController(options) {
11521
11521
  const d = await describe(effect, "transition");
11522
11522
  const duration = _nullishCoalesce(_nullishCoalesce(opts.duration, () => ( d.duration)), () => ( 600));
11523
11523
  if (!(duration > 0)) throw new Error("duration must be a number of ms above 0.");
11524
- const transition = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkRUZQMYXHcjs.resolveEffectValues.call(void 0, d, opts.values), duration };
11524
+ const transition = { effect: d.id, provider: slot.require().id, name: d.name, values: _chunkA3A5TI6Rcjs.resolveEffectValues.call(void 0, d, opts.values), duration };
11525
11525
  scenes2.update(sceneId, { transition });
11526
11526
  return transition;
11527
11527
  }
@@ -11665,7 +11665,7 @@ function createCanvasEffectsToolProvider(controller, options = {}) {
11665
11665
  const once = (instance) => {
11666
11666
  if (instance !== "effect") throw new Error('A shape node has one effect, "effect".');
11667
11667
  };
11668
- return _chunkRUZQMYXHcjs.createEffectToolProvider.call(void 0, {
11668
+ return _chunkA3A5TI6Rcjs.createEffectToolProvider.call(void 0, {
11669
11669
  id: _nullishCoalesce(options.id, () => ( "canvas-effects")),
11670
11670
  namespace: options.namespace,
11671
11671
  title: "Canvas effects",
@@ -209,7 +209,7 @@ import {
209
209
  EffectsSlot,
210
210
  createEffectToolProvider,
211
211
  resolveEffectValues
212
- } from "../chunk-7KWJEYIK.js";
212
+ } from "../chunk-6G2NLMVK.js";
213
213
  import "../chunk-V2ZCJRDM.js";
214
214
  import {
215
215
  brushstrokeScaleBucket
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkRUZQMYXHcjs = require('./chunk-RUZQMYXH.cjs');
6
+ var _chunkA3A5TI6Rcjs = require('./chunk-A3A5TI6R.cjs');
7
7
 
8
8
 
9
9
  var _chunkFJ2MSI43cjs = require('./chunk-FJ2MSI43.cjs');
@@ -26,7 +26,7 @@ function documentErrors(doc, description) {
26
26
  const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
27
27
  if (doc.values !== void 0) {
28
28
  if (!isRecord(doc.values)) errors.push({ path: "/values", message: "must be an object of control values" });
29
- else errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, doc.values).map((e) => ({ ...e, path: `/values${e.path}` })));
29
+ else errors.push(..._chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, doc.values).map((e) => ({ ...e, path: `/values${e.path}` })));
30
30
  }
31
31
  if (doc.tracks !== void 0) {
32
32
  if (!isRecord(doc.tracks)) errors.push({ path: "/tracks", message: "must be an object of keyframe lists" });
@@ -39,7 +39,7 @@ function documentErrors(doc, description) {
39
39
  kfs.forEach((kf, i) => {
40
40
  if (!isRecord(kf)) return errors.push({ path: `${at}/${i}`, message: "must be a keyframe object" });
41
41
  if (typeof kf.time !== "number" || !Number.isFinite(kf.time) || kf.time < 0) errors.push({ path: `${at}/${i}/time`, message: "must be a finite number of ms >= 0" });
42
- errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [control]: kf.value }).map((e) => ({ ...e, path: `${at}/${i}/value` })));
42
+ errors.push(..._chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, { [control]: kf.value }).map((e) => ({ ...e, path: `${at}/${i}/value` })));
43
43
  if (kf.easing !== void 0 && !_chunkDWJJI6RTcjs.isAnimationEasing.call(void 0, kf.easing)) errors.push({ path: `${at}/${i}/easing`, message: "is not a valid easing" });
44
44
  });
45
45
  }
@@ -49,7 +49,7 @@ function documentErrors(doc, description) {
49
49
  else
50
50
  doc.presets.forEach((p, i) => {
51
51
  if (!isRecord(p) || typeof p.name !== "string" || !p.name) return errors.push({ path: `/presets/${i}`, message: "must have a name" });
52
- errors.push(..._chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, p.values).map((e) => ({ ...e, path: `/presets/${i}/values${e.path}` })));
52
+ errors.push(..._chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, p.values).map((e) => ({ ...e, path: `/presets/${i}/values${e.path}` })));
53
53
  });
54
54
  }
55
55
  if (doc.duration !== void 0 && (typeof doc.duration !== "number" || !Number.isFinite(doc.duration) || !(doc.duration > 0))) {
@@ -170,7 +170,7 @@ async function createEffectAuthoringSession(source, options) {
170
170
  if (isDocumentSource(source)) {
171
171
  const doc = source.document;
172
172
  if (doc.version !== 1 || !doc.effectId) {
173
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Invalid effect authoring document: version 1 required");
173
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)("Invalid effect authoring document: version 1 required");
174
174
  }
175
175
  effectId = doc.effectId;
176
176
  initialDoc = doc;
@@ -178,15 +178,15 @@ async function createEffectAuthoringSession(source, options) {
178
178
  effectId = source.effectId;
179
179
  requestedPreset = source.preset;
180
180
  } else {
181
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Invalid effect authoring source");
181
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)("Invalid effect authoring source");
182
182
  }
183
183
  const description = await provider.describe(effectId);
184
184
  if (initialDoc) {
185
185
  const problems = documentErrors(initialDoc, description);
186
- if (problems.length) throw new (0, _chunkRUZQMYXHcjs.EffectControlError)(effectId, problems);
186
+ if (problems.length) throw new (0, _chunkA3A5TI6Rcjs.EffectControlError)(effectId, problems);
187
187
  }
188
188
  if (description.kind === "motion") {
189
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)("Motion effects cannot be authored as pixel effects", effectId);
189
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)("Motion effects cannot be authored as pixel effects", effectId);
190
190
  }
191
191
  let disposed = false;
192
192
  let playing = false;
@@ -207,7 +207,7 @@ async function createEffectAuthoringSession(source, options) {
207
207
  const found = description.presets.find((p) => p.name === requestedPreset);
208
208
  if (!found) {
209
209
  const validNames = description.presets.map((p) => `"${p.name}"`).join(", ");
210
- throw new (0, _chunkRUZQMYXHcjs.EffectUnsupportedError)(
210
+ throw new (0, _chunkA3A5TI6Rcjs.EffectUnsupportedError)(
211
211
  `Unknown preset "${requestedPreset}" for effect "${effectId}". Valid presets are: ${validNames || "none"}`,
212
212
  effectId
213
213
  );
@@ -321,7 +321,7 @@ async function createEffectAuthoringSession(source, options) {
321
321
  if (!validNames.includes(name)) {
322
322
  return { ok: false, message: `there is no control "${name}"; the controls are ${validNames.join(", ")}` };
323
323
  }
324
- const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [name]: value });
324
+ const errors = _chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, { [name]: value });
325
325
  if (errors.length > 0) {
326
326
  return { ok: false, message: valueErrorMessage(errors) };
327
327
  }
@@ -391,7 +391,7 @@ async function createEffectAuthoringSession(source, options) {
391
391
  if (!validNames.includes(k)) {
392
392
  return { ok: false, message: `there is no control "${k}"; the controls are ${validNames.join(", ")}` };
393
393
  }
394
- const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [k]: v });
394
+ const errors = _chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, { [k]: v });
395
395
  if (errors.length > 0) {
396
396
  return { ok: false, message: valueErrorMessage(errors) };
397
397
  }
@@ -528,7 +528,7 @@ async function createEffectAuthoringSession(source, options) {
528
528
  return { ok: false, message: `invalid easing: ${String(easing)}` };
529
529
  }
530
530
  const targetValue = value !== void 0 ? value : valuesAtTime(time, baseValues, tracks)[name];
531
- const errors = _chunkRUZQMYXHcjs.effectValueErrors.call(void 0, description, { [name]: targetValue });
531
+ const errors = _chunkA3A5TI6Rcjs.effectValueErrors.call(void 0, description, { [name]: targetValue });
532
532
  if (errors.length > 0) {
533
533
  return { ok: false, message: valueErrorMessage(errors) };
534
534
  }
@@ -705,7 +705,7 @@ async function createEffectAuthoringSession(source, options) {
705
705
  },
706
706
  async createPreview(opts) {
707
707
  if (disposed) {
708
- throw new (0, _chunkRUZQMYXHcjs.EffectDisposedError)(effectId);
708
+ throw new (0, _chunkA3A5TI6Rcjs.EffectDisposedError)(effectId);
709
709
  }
710
710
  let renderer;
711
711
  try {
@@ -730,7 +730,7 @@ async function createEffectAuthoringSession(source, options) {
730
730
  height: renderer.height,
731
731
  depth: renderer.depth,
732
732
  render: async (req) => {
733
- if (disposed) throw new (0, _chunkRUZQMYXHcjs.EffectDisposedError)(effectId);
733
+ if (disposed) throw new (0, _chunkA3A5TI6Rcjs.EffectDisposedError)(effectId);
734
734
  const effectiveValues = _nullishCoalesce(req.values, () => ( valuesAtTime(req.time, baseValues, tracks)));
735
735
  const pixels = await renderer.render({
736
736
  ...req,
@@ -68,7 +68,20 @@ function resolveEffectValues(description, values) {
68
68
  if (errors.length) throw new EffectControlError(description.id, errors);
69
69
  return { ...description.defaults, ...values ?? {} };
70
70
  }
71
- var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));
71
+ function normaliseEffectTag(tag) {
72
+ return tag.trim().toLowerCase().replace(/[\s_-]+/g, "-");
73
+ }
74
+ function hasEffectTags(e, tags) {
75
+ if (!tags?.length) return true;
76
+ const own = new Set((e.tags ?? []).map(normaliseEffectTag));
77
+ return tags.every((t) => own.has(normaliseEffectTag(t)));
78
+ }
79
+ function countEffectTags(effects) {
80
+ const counts = /* @__PURE__ */ new Map();
81
+ for (const e of effects) for (const tag of new Set((e.tags ?? []).map(normaliseEffectTag))) if (tag) counts.set(tag, (counts.get(tag) ?? 0) + 1);
82
+ return [...counts].map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
83
+ }
84
+ var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags);
72
85
  function searchEffectSummaries(catalog, query = "", options = {}) {
73
86
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
74
87
  const scored = [];
@@ -174,6 +187,12 @@ var object = (properties, required = []) => ({
174
187
  additionalProperties: false
175
188
  });
176
189
  var ENUM_LIMIT = 60;
190
+ var TAG_LIMIT = 40;
191
+ function nearestTags(tag, known) {
192
+ const t = normaliseEffectTag(tag);
193
+ const stem = t.slice(0, 4);
194
+ return known.filter((k) => k.tag.includes(t) || t.includes(k.tag) || stem.length === 4 && k.tag.startsWith(stem)).slice(0, 5).map((k) => k.tag);
195
+ }
177
196
  var summaryOut = (e) => ({
178
197
  id: e.id,
179
198
  name: e.name,
@@ -181,6 +200,7 @@ var summaryOut = (e) => ({
181
200
  category: e.category,
182
201
  uses: e.uses,
183
202
  ...e.description ? { description: e.description } : {},
203
+ ...e.tags?.length ? { tags: e.tags } : {},
184
204
  transparent: e.transparent,
185
205
  loop: e.loop,
186
206
  ...e.duration !== void 0 ? { duration: e.duration } : {}
@@ -241,21 +261,30 @@ function createEffectToolProvider(host) {
241
261
  {
242
262
  name: "search_effects",
243
263
  title: "Search effects",
244
- description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category or use (${EFFECT_USES.join(", ")}). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, transparent, loop, duration (ms) }], total }. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
264
+ description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms) }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
245
265
  inputSchema: object({
246
266
  query: { type: "string", description: "Words to match (default: every effect)." },
247
267
  kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG) or motion (animates vector art)." },
248
268
  category: { type: "string", minLength: 1, description: `The provider's category, e.g. "gradient", "light", "transition".` },
249
269
  use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input." },
270
+ tags: { type: "array", items: { type: "string", minLength: 1 }, description: "Only effects carrying every one of these tags, as search_effects lists them (any case; spaces count as hyphens)." },
250
271
  limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." }
251
272
  }),
252
273
  annotations: { title: "Search effects", ...ANNOTATIONS.read },
253
274
  handler: async (args) => {
254
275
  const p = host.provider();
255
276
  if (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };
256
- const found = await explain(async () => p.search(args.query ?? "", { kind: args.kind, category: args.category, use: args.use }));
277
+ const tags = args.tags;
278
+ const found = (await explain(async () => p.search(args.query ?? "", { kind: args.kind, category: args.category, use: args.use, tags }))).filter((e) => hasEffectTags(e, tags));
257
279
  const limit = args.limit ?? 20;
258
- return { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length };
280
+ const out = { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length, tags: countEffectTags(found).slice(0, TAG_LIMIT) };
281
+ if (tags?.length && !found.length) {
282
+ const known = countEffectTags(await explain(async () => p.list()));
283
+ const have = new Set(known.map((k) => k.tag));
284
+ const unknown = tags.filter((t) => !have.has(normaliseEffectTag(t)));
285
+ if (unknown.length) out.unknownTags = unknown.map((tag) => ({ tag, nearest: nearestTags(tag, known) }));
286
+ }
287
+ return out;
259
288
  }
260
289
  },
261
290
  {
@@ -374,6 +403,9 @@ export {
374
403
  isEffectError,
375
404
  effectValueErrors,
376
405
  resolveEffectValues,
406
+ normaliseEffectTag,
407
+ hasEffectTags,
408
+ countEffectTags,
377
409
  searchEffectSummaries,
378
410
  createEffectPixels,
379
411
  isEffectPixels,
@@ -68,7 +68,20 @@ function resolveEffectValues(description, values) {
68
68
  if (errors.length) throw new EffectControlError(description.id, errors);
69
69
  return { ...description.defaults, ..._nullishCoalesce(values, () => ( {})) };
70
70
  }
71
- var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));
71
+ function normaliseEffectTag(tag) {
72
+ return tag.trim().toLowerCase().replace(/[\s_-]+/g, "-");
73
+ }
74
+ function hasEffectTags(e, tags) {
75
+ if (!_optionalChain([tags, 'optionalAccess', _ => _.length])) return true;
76
+ const own = new Set((_nullishCoalesce(e.tags, () => ( []))).map(normaliseEffectTag));
77
+ return tags.every((t) => own.has(normaliseEffectTag(t)));
78
+ }
79
+ function countEffectTags(effects) {
80
+ const counts = /* @__PURE__ */ new Map();
81
+ for (const e of effects) for (const tag of new Set((_nullishCoalesce(e.tags, () => ( []))).map(normaliseEffectTag))) if (tag) counts.set(tag, (_nullishCoalesce(counts.get(tag), () => ( 0))) + 1);
82
+ return [...counts].map(([tag, count]) => ({ tag, count })).sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
83
+ }
84
+ var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use)) && hasEffectTags(e, o.tags);
72
85
  function searchEffectSummaries(catalog, query = "", options = {}) {
73
86
  const words = query.toLowerCase().split(/\s+/).filter(Boolean);
74
87
  const scored = [];
@@ -174,6 +187,12 @@ var object = (properties, required = []) => ({
174
187
  additionalProperties: false
175
188
  });
176
189
  var ENUM_LIMIT = 60;
190
+ var TAG_LIMIT = 40;
191
+ function nearestTags(tag, known) {
192
+ const t = normaliseEffectTag(tag);
193
+ const stem = t.slice(0, 4);
194
+ return known.filter((k) => k.tag.includes(t) || t.includes(k.tag) || stem.length === 4 && k.tag.startsWith(stem)).slice(0, 5).map((k) => k.tag);
195
+ }
177
196
  var summaryOut = (e) => ({
178
197
  id: e.id,
179
198
  name: e.name,
@@ -181,6 +200,7 @@ var summaryOut = (e) => ({
181
200
  category: e.category,
182
201
  uses: e.uses,
183
202
  ...e.description ? { description: e.description } : {},
203
+ ..._optionalChain([e, 'access', _2 => _2.tags, 'optionalAccess', _3 => _3.length]) ? { tags: e.tags } : {},
184
204
  transparent: e.transparent,
185
205
  loop: e.loop,
186
206
  ...e.duration !== void 0 ? { duration: e.duration } : {}
@@ -191,7 +211,7 @@ function createEffectToolProvider(host) {
191
211
  const notify = () => listeners.forEach((l) => l());
192
212
  const catalogFor = (provider) => {
193
213
  if (!provider) return null;
194
- if (_optionalChain([catalog, 'optionalAccess', _ => _.provider]) !== provider) {
214
+ if (_optionalChain([catalog, 'optionalAccess', _4 => _4.provider]) !== provider) {
195
215
  const entry = { provider, effects: null };
196
216
  catalog = entry;
197
217
  Promise.resolve().then(() => provider.list()).then((effects) => {
@@ -227,7 +247,7 @@ function createEffectToolProvider(host) {
227
247
  description: `${what}: control name \u2192 value, as listed by describe_effect (its "controls" schema: types, ranges, units and defaults). Only the given controls change; colours are "#rrggbb".${keyframes ? " A number control may instead be keyframes { keys: [{ t: ms from the start, v, ease }] }." : ""} Unknown controls and out-of-range values are refused with the valid ones.`
228
248
  });
229
249
  const effectIdSchema = (uses) => {
230
- const effects = _optionalChain([catalogFor, 'call', _2 => _2(host.provider()), 'optionalAccess', _3 => _3.filter, 'call', _4 => _4((e) => (e.kind !== "motion" || uses.includes("motion")) && e.uses.some((u) => uses.includes(u)))]);
250
+ const effects = _optionalChain([catalogFor, 'call', _5 => _5(host.provider()), 'optionalAccess', _6 => _6.filter, 'call', _7 => _7((e) => (e.kind !== "motion" || uses.includes("motion")) && e.uses.some((u) => uses.includes(u)))]);
231
251
  const base = `An effect id from search_effects (use ${uses.map((u) => `"${u}"`).join(" or ")}).`;
232
252
  return effects && effects.length && effects.length <= ENUM_LIMIT ? { type: "string", enum: effects.map((e) => e.id), description: `${base} One of: ${effects.map((e) => `${e.id} (${e.name})`).join(", ")}.` } : { type: "string", minLength: 1, description: base };
233
253
  };
@@ -241,21 +261,30 @@ function createEffectToolProvider(host) {
241
261
  {
242
262
  name: "search_effects",
243
263
  title: "Search effects",
244
- description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category or use (${EFFECT_USES.join(", ")}). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, transparent, loop, duration (ms) }], total }. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
264
+ description: `Searches the effects provider's catalog. Every word of query must appear in an effect's id, name, category, tags or description; filter by kind (shader, vector, motion), category, use (${EFFECT_USES.join(", ")}) or tags (effects carrying every one). For this editor, apply_effect takes uses ${host.applyUses.join(" or ")}. Returns { provider, effects: [{ id, name, kind, category, uses, description, tags, transparent, loop, duration (ms) }], total, tags: [{ tag, count }] }: tags lists the tags of every match, the most used first, so a broad search (or none) shows the tags to narrow by. A tag the catalog does not have comes back in unknownTags with the nearest ones. With no provider registered it returns available: false and the reason. Call describe_effect next for an effect's controls.`,
245
265
  inputSchema: object({
246
266
  query: { type: "string", description: "Words to match (default: every effect)." },
247
267
  kind: { type: "string", enum: [...EFFECT_KINDS], description: "shader (pixels), vector (animated SVG) or motion (animates vector art)." },
248
268
  category: { type: "string", minLength: 1, description: `The provider's category, e.g. "gradient", "light", "transition".` },
249
269
  use: { type: "string", enum: [...EFFECT_USES], description: "What the effect is for: filter changes a picture, transition blends two, generator draws a background, overlay draws over content, title draws a title, motion animates vector art, interactive reacts to input." },
270
+ tags: { type: "array", items: { type: "string", minLength: 1 }, description: "Only effects carrying every one of these tags, as search_effects lists them (any case; spaces count as hyphens)." },
250
271
  limit: { type: "integer", minimum: 1, maximum: 200, description: "Most effects to return (default 20)." }
251
272
  }),
252
273
  annotations: { title: "Search effects", ...ANNOTATIONS.read },
253
274
  handler: async (args) => {
254
275
  const p = host.provider();
255
276
  if (!p) return { available: false, reason: NO_PROVIDER, effects: [], total: 0 };
256
- const found = await explain(async () => p.search(_nullishCoalesce(args.query, () => ( "")), { kind: args.kind, category: args.category, use: args.use }));
277
+ const tags = args.tags;
278
+ const found = (await explain(async () => p.search(_nullishCoalesce(args.query, () => ( "")), { kind: args.kind, category: args.category, use: args.use, tags }))).filter((e) => hasEffectTags(e, tags));
257
279
  const limit = _nullishCoalesce(args.limit, () => ( 20));
258
- return { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length };
280
+ const out = { available: true, provider: { id: p.id, name: p.name, capabilities: p.capabilities }, effects: found.slice(0, limit).map(summaryOut), total: found.length, tags: countEffectTags(found).slice(0, TAG_LIMIT) };
281
+ if (_optionalChain([tags, 'optionalAccess', _8 => _8.length]) && !found.length) {
282
+ const known = countEffectTags(await explain(async () => p.list()));
283
+ const have = new Set(known.map((k) => k.tag));
284
+ const unknown = tags.filter((t) => !have.has(normaliseEffectTag(t)));
285
+ if (unknown.length) out.unknownTags = unknown.map((tag) => ({ tag, nearest: nearestTags(tag, known) }));
286
+ }
287
+ return out;
259
288
  }
260
289
  },
261
290
  {
@@ -348,16 +377,16 @@ function createEffectToolProvider(host) {
348
377
  ...host.description ? { description: host.description } : {},
349
378
  ...host.skill ? { skill: host.skill } : {},
350
379
  tools: () => {
351
- const extra = _nullishCoalesce(_optionalChain([host, 'access', _5 => _5.extraTools, 'optionalCall', _6 => _6(context)]), () => ( []));
352
- if (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => _optionalChain([t, 'access', _7 => _7.annotations, 'optionalAccess', _8 => _8.readOnlyHint]))];
380
+ const extra = _nullishCoalesce(_optionalChain([host, 'access', _9 => _9.extraTools, 'optionalCall', _10 => _10(context)]), () => ( []));
381
+ if (host.readOnlyReason()) return [...readTools(), ...extra.filter((t) => _optionalChain([t, 'access', _11 => _11.annotations, 'optionalAccess', _12 => _12.readOnlyHint]))];
353
382
  return [...readTools(), ...writeTools(), ...extra];
354
383
  },
355
384
  subscribe: (l) => {
356
385
  listeners.add(l);
357
- const off = _optionalChain([host, 'access', _9 => _9.subscribe, 'optionalCall', _10 => _10(l)]);
386
+ const off = _optionalChain([host, 'access', _13 => _13.subscribe, 'optionalCall', _14 => _14(l)]);
358
387
  return () => {
359
388
  listeners.delete(l);
360
- _optionalChain([off, 'optionalCall', _11 => _11()]);
389
+ _optionalChain([off, 'optionalCall', _15 => _15()]);
361
390
  };
362
391
  }
363
392
  };
@@ -385,4 +414,7 @@ function createEffectToolProvider(host) {
385
414
 
386
415
 
387
416
 
388
- exports.EFFECT_ERROR_CODES = EFFECT_ERROR_CODES; exports.EffectError = EffectError; exports.UnknownEffectError = UnknownEffectError; exports.EffectControlError = EffectControlError; exports.EffectUnsupportedError = EffectUnsupportedError; exports.EffectDisposedError = EffectDisposedError; exports.NoEffectsProviderError = NoEffectsProviderError; exports.isEffectError = isEffectError; exports.effectValueErrors = effectValueErrors; exports.resolveEffectValues = resolveEffectValues; exports.searchEffectSummaries = searchEffectSummaries; exports.createEffectPixels = createEffectPixels; exports.isEffectPixels = isEffectPixels; exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.encodeEffectFloat = encodeEffectFloat; exports.applyEffectMask = applyEffectMask; exports.EffectsSlot = EffectsSlot; exports.EFFECT_KINDS = EFFECT_KINDS; exports.EFFECT_USES = EFFECT_USES; exports.createEffectToolProvider = createEffectToolProvider;
417
+
418
+
419
+
420
+ exports.EFFECT_ERROR_CODES = EFFECT_ERROR_CODES; exports.EffectError = EffectError; exports.UnknownEffectError = UnknownEffectError; exports.EffectControlError = EffectControlError; exports.EffectUnsupportedError = EffectUnsupportedError; exports.EffectDisposedError = EffectDisposedError; exports.NoEffectsProviderError = NoEffectsProviderError; exports.isEffectError = isEffectError; exports.effectValueErrors = effectValueErrors; exports.resolveEffectValues = resolveEffectValues; exports.normaliseEffectTag = normaliseEffectTag; exports.hasEffectTags = hasEffectTags; exports.countEffectTags = countEffectTags; exports.searchEffectSummaries = searchEffectSummaries; exports.createEffectPixels = createEffectPixels; exports.isEffectPixels = isEffectPixels; exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.encodeEffectFloat = encodeEffectFloat; exports.applyEffectMask = applyEffectMask; exports.EffectsSlot = EffectsSlot; exports.EFFECT_KINDS = EFFECT_KINDS; exports.EFFECT_USES = EFFECT_USES; exports.createEffectToolProvider = createEffectToolProvider;