Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically declare *Target properties with TS #723

Open
mlwyatt opened this issue Sep 18, 2023 · 4 comments
Open

Dynamically declare *Target properties with TS #723

mlwyatt opened this issue Sep 18, 2023 · 4 comments

Comments

@mlwyatt
Copy link

mlwyatt commented Sep 18, 2023

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!

@marcoroth
Copy link
Member

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.

@mlwyatt
Copy link
Author

mlwyatt commented Sep 19, 2023

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

@ajaishankar
Copy link

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

@ajaishankar
Copy link

@mlwyatt give this a go

https://github.com/ajaishankar/stimulus-typescript

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants