0

I created a function returning a selection for each selector in an object

export default function multipleSelect<T extends string>(
  parent: ParentNode,
  selectors: Record<T, string>
) {
  const selected = {} as {
    [key in keyof typeof selectors]: Element
  }
  for (const [key, selector] of Object.entries(selectors) as [T, string][]) {
    const element = parent.querySelector(selector)
    if (!element) throw new Error(`parent doesn't contain '${key}' element`)
    selected[key] = element
  }
  return selected
}

usage:

const app = document.querySelector('.app')!
const elements = multipleSelect(app, {
  title: 'h3',
  list: 'ul',
  canvas: 'canvas',
  circle: 'circle',
  currentLi: 'ul>li.current',
})

Actually, the elements type is:

const element: {
  title: Element;
  list: Element;
  canvas: Element;
  circle: Element;
  currentLi: Element;
}

I would like to keep querySelector typing such that in my example the elements type is:

const element: {
    title: HTMLHeadingElement;
    list: HTMLUListElement;
    canvas: HTMLCanvasElement;
    circle: SVGCircleElement;
    currentLi: Element;
}
1
  • foo => currentLi right? Commented Oct 26, 2022 at 13:46

1 Answer 1

1

Change the generic constraint to Record<string, string> so you can also get the values with the keys.

export default function multipleSelect<T extends Record<string, string>>(
    parent: ParentNode,
    selectors: { [K in keyof T]: T[K] }, // make TS narrow to string literal types
): {
    [K in keyof T]: T[K] extends keyof HTMLElementTagNameMap // if HTML
        ? HTMLElementTagNameMap[T[K]]
        : T[K] extends keyof SVGElementTagNameMap // if SVG
        ? SVGElementTagNameMap[T[K]]
        : Element; // default to Element
} {
    const selected = {} as Record<keyof T, Element>;
   
    for (const [key, selector] of Object.entries(selectors) as [keyof T, string][]) {
        const element = parent.querySelector(selector);
        if (!element) throw new Error(`parent doesn't contain '${String(key)}' element`);
        selected[key] = element;
    }

    return selected as ReturnType<typeof multipleSelect<T>>;
}

Playground

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks it's perfect!! and in bonus i can us as to cast currentLi : multipleSelect(app, { currentLi: 'ul>li.current' as 'li' })
I realize how powerful the Typescript typing is but it is very hard to learn, would you have some hints to improve me?
@Yukulélé Sure, there's this new work-in-progress webpage that is essentially a guide through the type system and how to utilize it: type-level-typescript.com

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.