Repository: motion-canvas/motion-canvas
File: packages/2d/src/lib/decorators/vector2Signal.ts (TypeScript)

Code before:
export type Vector2LengthSignal<TOwner> = Signal<
  PossibleVector2<Length>,
  Vector2,
  TOwner
> & {
  x: Signal<Length, number, TOwner>;
  y: Signal<Length, number, TOwner>;
};

export function vector2Signal(
  prefix?: string | Record<string, string>,
): PropertyDecorator {
  return (target, key) => {
    compoundVector2Signal(
      typeof prefix === 'object'
        ? prefix
        : {
            x: prefix ? `${prefix}X` : 'x',
            y: prefix ? `${prefix}Y` : 'y',
          },
    )(target, key);
    wrapper(Vector2)(target, key);
  };
}

function compoundVector2Signal(
  entries: Record<string, string>,
): PropertyDecorator {
  return (target, key) => {
    const meta = getPropertyMetaOrCreate<any>(target, key);
    meta.compound = true;
    meta.compoundEntries = Object.entries(entries);

    addInitializer(target, (instance: any) => {
      if (!meta.parser) {
        useLogger().error(`Missing parser decorator for "${key.toString()}"`);
        return;
      }

      const initial = meta.default;
      const parser = meta.parser.bind(instance);
      const signalContext = new Vector2CompoundSignalContext(
        meta.compoundEntries.map(([key, property]) => {
          const signal = new SignalContext(
            modify(initial, value => parser(value)[key]),
            <any>map,
            instance,
            undefined,
            makeSignalExtensions(undefined, instance, property),
          ).toSignal();
          return [<keyof Vector2>key, signal];

Review comment (suggestion):
Instead of copying this whole function, you could modify the original `compound` to accept a custom signal class as its argument:
```ts
export function compound<
  TSetterValue,
  TValue extends TSetterValue,
  TKeys extends keyof TValue = keyof TValue,
  TOwner = void,
>(
  entries: Record<string, string>,
  klass: typeof CompoundSignalContext<TSetterValue, TValue, TKeys, TOwner> = CompoundSignalContext,
): PropertyDecorator {
  return (target, key) => {
    const meta = getPropertyMetaOrCreate<any>(target, key);
    meta.compound = true;
    meta.compoundEntries = Object.entries(entries);

    addInitializer(target, (instance: any) => {
      if (!meta.parser) {
        useLogger().error(`Missing parser decorator for "${key.toString()}"`);
        return;
      }

      const initial = meta.default;
      const parser = meta.parser.bind(instance);
      const signalContext = new klass(
        meta.compoundEntries.map(([key, property]) => {
          const signal = new SignalContext(
            modify(initial, value => parser(value)[key]),
            <any>map,
            instance,
            undefined,
            makeSignalExtensions(undefined, instance, property),
          ).toSignal();
          return [key as TKeys, signal];
        }),
        parser,
        initial,
        meta.interpolationFunction ?? deepLerp,
        instance,
        makeSignalExtensions(meta, instance, <string>key),
      );

      instance[key] = signalContext.toSignal();
    });
  };
}
```
`vector2Signal` could then pass `Vector2Context` as the second argument to `compound`

Code after:
import {
  PossibleVector2,
  Signal,
  Vector2,
  Vector2SignalContext,
} from '@motion-canvas/core';
import type {Length} from '../partials';
import {compound} from './compound';
import {wrapper} from './signal';

export type Vector2LengthSignal<TOwner> = Signal<
  PossibleVector2<Length>,
  Vector2,
  TOwner
> & {
  x: Signal<Length, number, TOwner>;
  y: Signal<Length, number, TOwner>;
};

export function vector2Signal(
  prefix?: string | Record<string, string>,
): PropertyDecorator {
  return (target, key) => {
    compound(
      typeof prefix === 'object'
        ? prefix
        : {
            x: prefix ? `${prefix}X` : 'x',
            y: prefix ? `${prefix}Y` : 'y',
          },
      Vector2SignalContext,
    )(target, key);
    wrapper(Vector2)(target, key);
  };
}