Skip to content

Commit 0333462

Browse files
committed
refactor: align getElement/getElements generics with one/many pattern
Switch from `E extends keyof HTMLElementTagNameMap` to separate overloads: tag name for auto-inference, `E extends Element` for explicit typing, and a plain fallback — matching the ref builder API.
1 parent 2c551a4 commit 0333462

2 files changed

Lines changed: 48 additions & 22 deletions

File tree

packages/nanotags/src/setup-context.test.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,25 @@ describe("getElement / getElements", () => {
286286
);
287287
expect(found).toHaveLength(2);
288288
});
289+
290+
it("accepts custom element type via generic", () => {
291+
type CustomEl = HTMLElement & { custom: true };
292+
const tag = uniqueTag("ge");
293+
define(tag, (ctx) => {
294+
const single = ctx.getElement<CustomEl>(".item");
295+
expectTypeOf(single).toEqualTypeOf<CustomEl>();
296+
297+
const list = ctx.getElements<CustomEl>(".item");
298+
expectTypeOf(list).toEqualTypeOf<CustomEl[]>();
299+
300+
const container = ctx.getElement(".container");
301+
const scoped = ctx.getElement<CustomEl>(container, ".item");
302+
expectTypeOf(scoped).toEqualTypeOf<CustomEl>();
303+
304+
const scopedList = ctx.getElements<CustomEl>(container, ".item");
305+
expectTypeOf(scopedList).toEqualTypeOf<CustomEl[]>();
306+
});
307+
});
289308
});
290309

291310
describe("effect", () => {
@@ -552,7 +571,7 @@ describe("bind", () => {
552571
const parentTag = uniqueTag("bind-parent");
553572
const $val = atom("from-parent");
554573
define(parentTag, (ctx) => {
555-
const child = ctx.getElement(childTag);
574+
const child = ctx.getElement<HTMLElement>(childTag);
556575
ctx.bind($val, child, { prop: "value", event: "change" });
557576
});
558577
const el = mount(`<${parentTag}><${childTag}></${childTag}></${parentTag}>`);
@@ -595,7 +614,7 @@ describe("bind", () => {
595614
const tag = uniqueTag("bind");
596615
const $theme = atom("dark");
597616
define(tag, (ctx) => {
598-
const ctrl = ctx.getElement(controlTag);
617+
const ctrl = ctx.getElement<HTMLElement>(controlTag);
599618
ctx.bind($theme, ctrl, { prop: "theme", event: "change" });
600619
});
601620
const el = mount(`<${tag}><${controlTag}></${controlTag}></${tag}>`);

packages/nanotags/src/setup-context.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,34 +147,41 @@ export class Context<
147147
}
148148

149149
/** Queries a single required element by CSS selector. Throws if not found. */
150-
getElement<E extends keyof HTMLElementTagNameMap>(selector: E | string): HTMLElementTagNameMap[E];
151-
getElement<E extends keyof HTMLElementTagNameMap>(
150+
getElement<const Tag extends keyof HTMLElementTagNameMap>(
151+
selector: Tag,
152+
): HTMLElementTagNameMap[Tag];
153+
getElement<const Tag extends keyof HTMLElementTagNameMap>(
152154
root: DocumentFragment | Element,
153-
selector: E | string,
154-
): HTMLElementTagNameMap[E];
155-
getElement<E extends keyof HTMLElementTagNameMap>(
156-
selectorOrRoot: E | string | DocumentFragment | Element,
157-
maybeSelector?: E | string,
158-
): HTMLElementTagNameMap[E] {
159-
return this.getElements<E>(selectorOrRoot as any, maybeSelector as any)[0]!;
155+
selector: Tag,
156+
): HTMLElementTagNameMap[Tag];
157+
getElement<E extends Element>(selector: string): E;
158+
getElement<E extends Element>(root: DocumentFragment | Element, selector: string): E;
159+
getElement(selector: string): Element;
160+
getElement(root: DocumentFragment | Element, selector: string): Element;
161+
getElement(selectorOrRoot: string | DocumentFragment | Element, maybeSelector?: string): Element {
162+
return this.getElements(selectorOrRoot as any, maybeSelector as any)[0]!;
160163
}
161164

162165
/** Queries all matching elements by CSS selector. Throws if none found. */
163-
getElements<E extends keyof HTMLElementTagNameMap>(
164-
selector: E | string,
165-
): HTMLElementTagNameMap[E][];
166-
getElements<E extends keyof HTMLElementTagNameMap>(
166+
getElements<const Tag extends keyof HTMLElementTagNameMap>(
167+
selector: Tag,
168+
): HTMLElementTagNameMap[Tag][];
169+
getElements<const Tag extends keyof HTMLElementTagNameMap>(
167170
root: DocumentFragment | Element,
168-
selector: E | string,
169-
): HTMLElementTagNameMap[E][];
170-
getElements<E extends keyof HTMLElementTagNameMap>(
171-
selectorOrRoot: E | string | DocumentFragment | Element,
172-
maybeSelector?: E | string,
173-
): HTMLElementTagNameMap[E][] {
171+
selector: Tag,
172+
): HTMLElementTagNameMap[Tag][];
173+
getElements<E extends Element>(selector: string): E[];
174+
getElements<E extends Element>(root: DocumentFragment | Element, selector: string): E[];
175+
getElements(selector: string): Element[];
176+
getElements(root: DocumentFragment | Element, selector: string): Element[];
177+
getElements(
178+
selectorOrRoot: string | DocumentFragment | Element,
179+
maybeSelector?: string,
180+
): Element[] {
174181
const hasRoot = maybeSelector !== undefined;
175182
const root = hasRoot ? (selectorOrRoot as DocumentFragment | Element) : this.host;
176183
const selector = (hasRoot ? maybeSelector : selectorOrRoot) as string;
177-
const elements = Array.from(root.querySelectorAll<HTMLElementTagNameMap[E]>(selector));
184+
const elements = Array.from(root.querySelectorAll(selector));
178185
invariant(elements.length > 0, `${this.host.localName}: missing ${selector}`);
179186
return elements;
180187
}

0 commit comments

Comments
 (0)