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;
}
CodePudding user response:
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>>;
}