hotwired / stimulus

A modest JavaScript framework for the HTML you already have

Home Page:https://stimulus.hotwired.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Dynamically declare `*Target` properties with TS

mlwyatt opened this issue · comments

I've looked over #121 and #466 and while they are similar, they aren't quite what I'm looking for. I know when using TS I need to declare the *Target properties I want to use. I'm fine with that but I'm wondering if that can done dynamically.

declareing all of the properties isn't too bad when there's only 1 or 2 targets, but sometimes I have numerous targets and would like to avoid declareing every one of them when they're all very similar. It's a similar situation for values and outlets but I use those far less than targets

Is there a way to dynamically declare these properties or to tell TS that Controller does correctly implement ControllerWithTarget<Targets> below (while avoiding @ts-ignore and similar) ?

import { Controller } from '@hotwired/stimulus';

namespace Transform {
  export type HasTarget<T> = {
    [K in keyof T as `has${Capitalize<string & K>}Target`]: boolean;
  };
  export type Target<T> = {
    [K in keyof T as `${string & K}Target`]: T[K];
  };
  export type Targets<T> = {
    [K in keyof T as `${string & K}Targets`]: Array<T[K]>;
  };
}

const targets = [
  'button',
  'container',
] as const;

type TargetList = typeof targets;

interface Targets extends Record<TargetList[number], HTMLElement> {
  button: HTMLButtonElement;
  container: HTMLDivElement;
}

type ControllerWithTarget<T> =
  & Transform.HasTarget<T>
  & Transform.Target<T>
  & Transform.Targets<T>;

// Trying to achieve something like this
export default class extends Controller implements ControllerWithTarget<Targets> {
  static targets = [...targets];

  // declare readonly hasButtonTarget: boolean;
  // declare readonly buttonTarget: HTMLButtonElement;
  // declare readonly buttonTargets: Array<HTMLButtonElement>;

  // declare readonly hasContainerTarget: boolean;
  // declare readonly containerTarget: HTMLDivElement;
  // declare readonly containerTargets: Array<HTMLDivElement>;

  disableButton(): void {
    if ( this.hasButtonTarget ) {
      this.buttonTarget.disabled = true;
    }
  }
}

Any help is greatly appreciated, thanks!

Hey @mlwyatt, I know this might not be exactly what you are looking for, but I find the pattern proposed in the stimulus-decorators package kinda elegant.

Hey @mlwyatt, I know this might not be exactly what you are looking for, but I find the pattern proposed in the stimulus-decorators package kinda elegant.

Thanks for reaching out! I agree that it's elegant but it's also a bit outdated (no support for outlets) and not quite what I'm after. I think what I want just isn't possible with the current state of TS

@mlwyatt see #724 for a possible way to implement this...