From ca26720d12949cb5dd1396ec12f5996743132594 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:26:03 -0400 Subject: [PATCH 01/27] Add test for custom element types --- .../ts-template-imports-app/src/custom-elements.gts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test-packages/ts-template-imports-app/src/custom-elements.gts diff --git a/test-packages/ts-template-imports-app/src/custom-elements.gts b/test-packages/ts-template-imports-app/src/custom-elements.gts new file mode 100644 index 00000000..85f49581 --- /dev/null +++ b/test-packages/ts-template-imports-app/src/custom-elements.gts @@ -0,0 +1,10 @@ + +const two = 2; +const str = "hello"; + +export const UsesCustomElement = ; \ No newline at end of file From 6f6994cf86e00128cea6309105c194c01c4afaff Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:19:40 -0500 Subject: [PATCH 02/27] Fix support for custom elements --- .vscode/launch.json | 1 + .../-private/dsl/custom-elements.d.ts | 33 +++++++++++++++++++ packages/template/-private/dsl/emit.d.ts | 2 +- packages/template/-private/dsl/types.d.ts | 4 ++- .../template/__tests__/attributes.test.ts | 23 +++++++++++++ .../ember-app/src/custom-elements.gts | 20 +++++++++++ .../__tests__/transform/rewrite.test.ts | 2 +- .../transform/template-to-typescript.test.ts | 13 ++++++++ .../src/custom-elements.gts | 12 ++++++- 9 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 packages/template/-private/dsl/custom-elements.d.ts create mode 100644 packages/vscode/__fixtures__/ember-app/src/custom-elements.gts diff --git a/.vscode/launch.json b/.vscode/launch.json index 519f172c..0d45b283 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,7 @@ // comment to activate your local extensions "--disable-extensions", "${workspaceFolder}/packages/vscode/__fixtures__/ember-app" + ] }, { diff --git a/packages/template/-private/dsl/custom-elements.d.ts b/packages/template/-private/dsl/custom-elements.d.ts new file mode 100644 index 00000000..9438a6a1 --- /dev/null +++ b/packages/template/-private/dsl/custom-elements.d.ts @@ -0,0 +1,33 @@ +declare global { + /** + * Map of element tag names to their type. Used by `emitElement` via `ElementForTagName`. + * + * By default, this interface is empty; to add custom elements, you can + * augment it in your own project like so: + * ```ts + * declare global { + * interface GlintCustomElements { + * 'my-custom-element': MyCustomElementClass; + * } + * } + * ``` + * + * When doing this, you'll also want your props and attributes to be typed, and that is configured + * through a separate declaration merge as TypeScript doesn't have a way of accessing which attributes/props + * are valid for a given element type. + * + * ```ts + * declare global { + * interface GlintHtmlElementAttributesMap { + * 'my-custom-element': { + * propNum: number; + * propStr: string; + * }; + * } + * } + * ``` + */ + interface GlintCustomElements { + /* intentionally empty, as there are no custom elements by default */ + } +} \ No newline at end of file diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index 2d975683..acdede02 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -1,4 +1,4 @@ -import { AttrValue, ContentValue } from '..'; +import { ContentValue } from '..'; import { ComponentReturn, AnyContext, diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index d2efe711..c245ecf0 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -36,7 +36,9 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - : Element; + : Name extends keyof GlintCustomElements + ? GlintCustomElements[Name] + : Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Name] diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index 4db4d340..537c575f 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -1,3 +1,4 @@ +import '@glint/template'; import { htmlSafe } from '@ember/template'; import { expectTypeOf } from 'expect-type'; import { @@ -61,6 +62,28 @@ class MyComponent extends TestComponent<{ Element: HTMLImageElement }> { expectTypeOf(el).toEqualTypeOf<{ element: Element }>(); } +class RegisteredCustomElement extends HTMLElement {} +interface RegisteredCustomElementAttributes { + propNum: number; + propStr: string; +} + + +declare global { + interface GlintCustomElements { + 'registered-custom-element': RegisteredCustomElement; + } + + interface GlintHtmlElementAttributesMap { + 'registered-custom-element': RegisteredCustomElementAttributes; + } +} + +{ + const el = emitElement('registered-custom-element'); + expectTypeOf(el).toEqualTypeOf<{ element: RegisteredCustomElement }>(); +} + /** * ```handlebars * diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts b/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts new file mode 100644 index 00000000..4eafdc72 --- /dev/null +++ b/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts @@ -0,0 +1,20 @@ + +import '@glint/template'; +declare global { + interface GlintHtmlElementAttributesMap { + 'my-custom-element': { + propNum: number; + propStr: string; + }; + } +} + +const two = 2; +const str = "hello"; + +export const UsesCustomElement = ; \ No newline at end of file diff --git a/test-packages/package-test-core/__tests__/transform/rewrite.test.ts b/test-packages/package-test-core/__tests__/transform/rewrite.test.ts index f76bb1b8..e932b685 100644 --- a/test-packages/package-test-core/__tests__/transform/rewrite.test.ts +++ b/test-packages/package-test-core/__tests__/transform/rewrite.test.ts @@ -139,7 +139,7 @@ describe('Transform: rewriteModule', () => { }); }); - describe({}, () => { + describe('with loaded environment', () => { test('in class extends', () => { let customEnv = GlintEnvironment.load({}); let script = { diff --git a/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts b/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts index 707dea5a..79c66321 100644 --- a/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts +++ b/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts @@ -984,6 +984,19 @@ describe('Transform: rewriteTemplate', () => { }); }); + describe('custom elements', () => { + test('with programmatic contents', () => { + let template = '{{@foo}}'; + + expect(templateBody(template)).toMatchInlineSnapshot(` + "{ + const __glintY__ = __glintDSL__.emitElement("my-element"); + __glintDSL__.emitContent(__glintDSL__.resolveOrReturn(__glintRef__.args.foo)()); + }" + `); + }); + }); + describe('angle bracket components', () => { test('self-closing', () => { let template = ``; diff --git a/test-packages/ts-template-imports-app/src/custom-elements.gts b/test-packages/ts-template-imports-app/src/custom-elements.gts index 85f49581..4eafdc72 100644 --- a/test-packages/ts-template-imports-app/src/custom-elements.gts +++ b/test-packages/ts-template-imports-app/src/custom-elements.gts @@ -1,10 +1,20 @@ +import '@glint/template'; +declare global { + interface GlintHtmlElementAttributesMap { + 'my-custom-element': { + propNum: number; + propStr: string; + }; + } +} + const two = 2; const str = "hello"; export const UsesCustomElement = ; \ No newline at end of file From bc450e8aa900561b2462139c1dd916e43342fe5d Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:23:49 -0500 Subject: [PATCH 03/27] Finish --- docs/glint-types.md | 45 +++++++++++++ .../-private/dsl/custom-elements.d.ts | 64 +++++++++---------- packages/template/-private/dsl/types.d.ts | 2 +- .../template/__tests__/attributes.test.ts | 3 +- 4 files changed, 79 insertions(+), 35 deletions(-) diff --git a/docs/glint-types.md b/docs/glint-types.md index 91a6b936..8028289a 100644 --- a/docs/glint-types.md +++ b/docs/glint-types.md @@ -207,3 +207,48 @@ declare global { } } ``` + +### Custom Elements / WebComponent types + +To add custom elements, you can augment a global interface like so: +```ts +import '@glint/template'; + +import type { MyCustomElementClass } from './wherever.ts'; + +declare global { + interface GlintCustomElements { + 'my-custom-element': MyCustomElementClass; + } +} +``` + +When doing this, you'll also want your props and attributes to be typed, and that is configured +through a separate declaration, `GlintHtmlElementAttributesMap` + +```ts +declare global { + interface GlintHtmlElementAttributesMap { + 'my-custom-element': { + propNum: number; + propStr: string; + }; + } +} +``` + +And they can be combined, if desired: +```ts +import '@glint/template'; + +import type { MyCustomElementClass, MyCustomElementProps } from './wherever.ts'; + +declare global { + interface GlintCustomElements { + 'my-custom-element': MyCustomElementClass; + } + interface GlintHtmlElementAttributesMap { + 'my-custom-element': MyCustomElementProps; + } +} +``` \ No newline at end of file diff --git a/packages/template/-private/dsl/custom-elements.d.ts b/packages/template/-private/dsl/custom-elements.d.ts index 9438a6a1..8ce3960e 100644 --- a/packages/template/-private/dsl/custom-elements.d.ts +++ b/packages/template/-private/dsl/custom-elements.d.ts @@ -1,33 +1,33 @@ declare global { - /** - * Map of element tag names to their type. Used by `emitElement` via `ElementForTagName`. - * - * By default, this interface is empty; to add custom elements, you can - * augment it in your own project like so: - * ```ts - * declare global { - * interface GlintCustomElements { - * 'my-custom-element': MyCustomElementClass; - * } - * } - * ``` - * - * When doing this, you'll also want your props and attributes to be typed, and that is configured - * through a separate declaration merge as TypeScript doesn't have a way of accessing which attributes/props - * are valid for a given element type. - * - * ```ts - * declare global { - * interface GlintHtmlElementAttributesMap { - * 'my-custom-element': { - * propNum: number; - * propStr: string; - * }; - * } - * } - * ``` - */ - interface GlintCustomElements { - /* intentionally empty, as there are no custom elements by default */ - } -} \ No newline at end of file + /** + * Map of element tag names to their type. Used by `emitElement` via `ElementForTagName`. + * + * By default, this interface is empty; to add custom elements, you can + * augment it in your own project like so: + * ```ts + * declare global { + * interface GlintCustomElements { + * 'my-custom-element': MyCustomElementClass; + * } + * } + * ``` + * + * When doing this, you'll also want your props and attributes to be typed, and that is configured + * through a separate declaration merge as TypeScript doesn't have a way of accessing which attributes/props + * are valid for a given element type. + * + * ```ts + * declare global { + * interface GlintHtmlElementAttributesMap { + * 'my-custom-element': { + * propNum: number; + * propStr: string; + * }; + * } + * } + * ``` + */ + interface GlintCustomElements { + /* intentionally empty, as there are no custom elements by default */ + } +} diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index c245ecf0..848ea533 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -36,7 +36,7 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - : Name extends keyof GlintCustomElements + : Name extends keyof GlintCustomElements ? GlintCustomElements[Name] : Element; diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index 537c575f..4e255182 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -68,14 +68,13 @@ interface RegisteredCustomElementAttributes { propStr: string; } - declare global { interface GlintCustomElements { 'registered-custom-element': RegisteredCustomElement; } interface GlintHtmlElementAttributesMap { - 'registered-custom-element': RegisteredCustomElementAttributes; + 'registered-custom-element': RegisteredCustomElementAttributes; } } From a990d53bde1bf485b3682d7860489a1f195b12cb Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 3 Nov 2025 12:39:30 -0500 Subject: [PATCH 04/27] Update test --- .../ember-app/src/custom-elements.gts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts b/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts index 4eafdc72..49aa829c 100644 --- a/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts +++ b/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts @@ -1,6 +1,15 @@ import '@glint/template'; + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + declare global { + interface GlintCustomElements { + 'my-custom-element': MyCustomElement; + } interface GlintHtmlElementAttributesMap { 'my-custom-element': { propNum: number; @@ -13,8 +22,15 @@ const two = 2; const str = "hello"; export const UsesCustomElement = ; \ No newline at end of file From f9a328edfd27e80b87f6a9751f93df31a3242a46 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:32:47 -0500 Subject: [PATCH 05/27] Add another type test --- .../private/ElementForTagName.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packages/template/__tests__/private/ElementForTagName.test.ts diff --git a/packages/template/__tests__/private/ElementForTagName.test.ts b/packages/template/__tests__/private/ElementForTagName.test.ts new file mode 100644 index 00000000..8be2d18f --- /dev/null +++ b/packages/template/__tests__/private/ElementForTagName.test.ts @@ -0,0 +1,25 @@ +import '@glint/template'; + +import { expectTypeOf } from 'expect-type'; +import type { ElementForTagName } from '../../-private/dsl/types'; + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + +declare global { + interface GlintCustomElements { + 'my-custom-element-element-for-tag-name': MyCustomElement; + } +} + +{ + type X = ElementForTagName<'my-custom-element-element-for-tag-name'>; + + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + expectTypeOf().not.toEqualTypeOf(); +} \ No newline at end of file From 7fab501ed1988689f5983e9b6091f44ededf7198 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:37:36 -0500 Subject: [PATCH 06/27] More tests --- .../template/__tests__/emit-element.test.ts | 22 +++++++++++++++++++ .../private/ElementForTagName.test.ts | 22 +++++++++---------- 2 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 packages/template/__tests__/emit-element.test.ts diff --git a/packages/template/__tests__/emit-element.test.ts b/packages/template/__tests__/emit-element.test.ts new file mode 100644 index 00000000..47b8698e --- /dev/null +++ b/packages/template/__tests__/emit-element.test.ts @@ -0,0 +1,22 @@ +import '@glint/template'; + +import { expectTypeOf } from 'expect-type'; +import { emitElement } from '../-private/dsl'; + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + +declare global { + interface GlintCustomElements { + 'my-custom-element-emit-element': MyCustomElement; + } +} + +const div = emitElement('div'); +expectTypeOf(div).toEqualTypeOf<{ element: HTMLDivElement }>(); + +const custom = emitElement('my-custom-element-emit-element'); + +expectTypeOf(custom).toEqualTypeOf<{ element: MyCustomElement }>(); diff --git a/packages/template/__tests__/private/ElementForTagName.test.ts b/packages/template/__tests__/private/ElementForTagName.test.ts index 8be2d18f..e93ec296 100644 --- a/packages/template/__tests__/private/ElementForTagName.test.ts +++ b/packages/template/__tests__/private/ElementForTagName.test.ts @@ -4,22 +4,22 @@ import { expectTypeOf } from 'expect-type'; import type { ElementForTagName } from '../../-private/dsl/types'; class MyCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; + propNum!: number; + propStr!: string; } declare global { - interface GlintCustomElements { - 'my-custom-element-element-for-tag-name': MyCustomElement; - } + interface GlintCustomElements { + 'my-custom-element-element-for-tag-name': MyCustomElement; + } } { - type X = ElementForTagName<'my-custom-element-element-for-tag-name'>; + type X = ElementForTagName<'my-custom-element-element-for-tag-name'>; - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); - expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); - expectTypeOf().not.toEqualTypeOf(); -} \ No newline at end of file + expectTypeOf().not.toEqualTypeOf(); +} From 9b8997721522bb40104b64171392a3bce2f35e75 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:39:33 -0500 Subject: [PATCH 07/27] Fix --- bin/build-augmentations.mjs | 5 +- .../-private/dsl/lib.dom.augmentation.d.ts | 5 +- packages/template/-private/dsl/types.d.ts | 3 +- .../template/__tests__/custom-element.test.ts | 71 ++++++++ .../template/__tests__/emit-element.test.ts | 22 --- .../__fixtures__/ember-app/package.json | 9 +- .../ember-app/src/custom-elements.gts | 36 ---- .../__fixtures__/ember-app/tsconfig.json | 5 +- pnpm-lock.yaml | 169 ++++++++++++++++++ pnpm-workspace.yaml | 1 + 10 files changed, 261 insertions(+), 65 deletions(-) create mode 100644 packages/template/__tests__/custom-element.test.ts delete mode 100644 packages/template/__tests__/emit-element.test.ts delete mode 100644 packages/vscode/__fixtures__/ember-app/src/custom-elements.gts diff --git a/bin/build-augmentations.mjs b/bin/build-augmentations.mjs index fd8eeb28..14cafeb6 100644 --- a/bin/build-augmentations.mjs +++ b/bin/build-augmentations.mjs @@ -44,6 +44,7 @@ const prefix = `// generated by /bin/build-augmentations.mjs // The TypeScript lib.dom.d.ts already has HTMLElementTagNameMap, // but it does not provide unique types for each element, and the technique // we use for looking up the type-string for each element does not work with the built in types. + `; const filePath = resolve( fileURLToPath(import.meta.url), @@ -51,7 +52,7 @@ const filePath = resolve( ); let content = prefix; content += '\n'; -content += `export interface GlintElementRegistry {\n`; +content += `declare interface GlintElementRegistry {\n`; content += registry.join('\n') + '\n'; -content += `}\n`; +content += `} \n`; writeFileSync(filePath, content); diff --git a/packages/template/-private/dsl/lib.dom.augmentation.d.ts b/packages/template/-private/dsl/lib.dom.augmentation.d.ts index b4df69f6..19289b49 100644 --- a/packages/template/-private/dsl/lib.dom.augmentation.d.ts +++ b/packages/template/-private/dsl/lib.dom.augmentation.d.ts @@ -7,7 +7,8 @@ // but it does not provide unique types for each element, and the technique // we use for looking up the type-string for each element does not work with the built in types. -export interface GlintElementRegistry { + +declare interface GlintElementRegistry { 'SVGElement': SVGElement; 'HTMLAnchorElement': HTMLAnchorElement; 'HTMLElement': HTMLElement; @@ -135,4 +136,4 @@ export interface GlintElementRegistry { 'SVGTSpanElement': SVGTSpanElement; 'SVGUseElement': SVGUseElement; 'SVGViewElement': SVGViewElement; -} +} diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 848ea533..d6ef80a0 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -1,7 +1,8 @@ import './elements'; +import './lib.dom.augmentation' import { AttrValue } from '../index'; -import { GlintElementRegistry } from './lib.dom.augmentation'; +// Defined in lib.dom.augmentation.d.ts type Registry = GlintElementRegistry; /** diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts new file mode 100644 index 00000000..cf6387e7 --- /dev/null +++ b/packages/template/__tests__/custom-element.test.ts @@ -0,0 +1,71 @@ +import '@glint/template'; + +import { expectTypeOf } from 'expect-type'; +import { emitElement, applyAttributes, AttributesForElement, Lookup } from '../-private/dsl'; + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + + +declare global { + interface GlintCustomElements { + 'my-custom-element-emit-element': MyCustomElement; + } + + interface GlintElementRegistry { + 'MyCustomElement': MyCustomElement; + } + + interface GlintHtmlElementAttributesMap { + 'MyCustomElement': { + propNum: number; + propStr: string; + }; + } +} + +/** + * Baseline + */ +{ + const div = emitElement('div'); + expectTypeOf(div).toEqualTypeOf<{ element: HTMLDivElement }>(); + expectTypeOf().toEqualTypeOf(); + + applyAttributes(div.element, { + 'data-foo': 123, + 'role': 'button', +}); + +} + +/** + * Can we have typed custom-elements? + * (yes) + */ +{ + + expectTypeOf().toHaveProperty('MyCustomElement'); + expectTypeOf().toHaveProperty('my-custom-element-emit-element'); + expectTypeOf().toHaveProperty('MyCustomElement'); + + + const custom = emitElement('my-custom-element-emit-element'); + expectTypeOf(custom).toEqualTypeOf<{ element: MyCustomElement }>(); + expectTypeOf().toEqualTypeOf(); + + type ElementName = Lookup + expectTypeOf().toEqualTypeOf<'MyCustomElement'>(); + + type FoundAttrs = AttributesForElement; + expectTypeOf().not.toBeNever(); + expectTypeOf().toHaveProperty('propNum'); + + applyAttributes(custom.element, { + propNum: 123, + propStr: 'hello', + }); +} + \ No newline at end of file diff --git a/packages/template/__tests__/emit-element.test.ts b/packages/template/__tests__/emit-element.test.ts deleted file mode 100644 index 47b8698e..00000000 --- a/packages/template/__tests__/emit-element.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import '@glint/template'; - -import { expectTypeOf } from 'expect-type'; -import { emitElement } from '../-private/dsl'; - -class MyCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; -} - -declare global { - interface GlintCustomElements { - 'my-custom-element-emit-element': MyCustomElement; - } -} - -const div = emitElement('div'); -expectTypeOf(div).toEqualTypeOf<{ element: HTMLDivElement }>(); - -const custom = emitElement('my-custom-element-emit-element'); - -expectTypeOf(custom).toEqualTypeOf<{ element: MyCustomElement }>(); diff --git a/packages/vscode/__fixtures__/ember-app/package.json b/packages/vscode/__fixtures__/ember-app/package.json index 80dea529..8c914e51 100644 --- a/packages/vscode/__fixtures__/ember-app/package.json +++ b/packages/vscode/__fixtures__/ember-app/package.json @@ -1,4 +1,11 @@ { "name": "ember-app", - "private": true + "private": true, + "devDependencies": { + "@glint/ember-tsc": "workspace:*", + "@glint/template": "workspace:*", + "@glint/tsserver-plugin": "workspace:*", + "ember-source": "6.8.1", + "typescript": "^" + } } diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts b/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts deleted file mode 100644 index 49aa829c..00000000 --- a/packages/vscode/__fixtures__/ember-app/src/custom-elements.gts +++ /dev/null @@ -1,36 +0,0 @@ - -import '@glint/template'; - -class MyCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; -} - -declare global { - interface GlintCustomElements { - 'my-custom-element': MyCustomElement; - } - interface GlintHtmlElementAttributesMap { - 'my-custom-element': { - propNum: number; - propStr: string; - }; - } -} - -const two = 2; -const str = "hello"; - -export const UsesCustomElement = ; \ No newline at end of file diff --git a/packages/vscode/__fixtures__/ember-app/tsconfig.json b/packages/vscode/__fixtures__/ember-app/tsconfig.json index 8d5cf2c5..7db53c36 100644 --- a/packages/vscode/__fixtures__/ember-app/tsconfig.json +++ b/packages/vscode/__fixtures__/ember-app/tsconfig.json @@ -1,6 +1,9 @@ { "extends": "../../../../tsconfig.compileroptions.json", "compilerOptions": { - "baseUrl": "." + "moduleResolution": "bundler", + "module": "esnext", + "baseUrl": ".", + "types": ["ember-source/types"] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be3e596a..5a954b09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,6 +321,21 @@ importers: specifier: ^0.5.0 version: 0.5.5 + packages/vscode/__fixtures__/ember-app: + devDependencies: + '@glint/ember-tsc': + specifier: workspace:* + version: link:../../../core + '@glint/template': + specifier: workspace:* + version: link:../../../template + '@glint/tsserver-plugin': + specifier: workspace:* + version: link:../../../tsserver-plugin + ember-source: + specifier: 6.8.1 + version: 6.8.1 + test-packages/package-test-core: devDependencies: '@glimmer/component': @@ -2113,6 +2128,10 @@ packages: resolution: {integrity: sha512-SrWiaKM3AND2FQ732wtjAKol7XhCnRqit3tJShG4X0mT27Jb3zuhTI2dkfYVVMTJ23pjT/+0y+s/uGaBSirnBg==} engines: {node: '>= 18.0.0'} + '@glimmer/compiler@0.94.11': + resolution: {integrity: sha512-t9eyLZIFsiwAib8Zyfu67yBep5Vn2bd5DScIE2hharPE/OKKI7cpQYi6BzQhSGYEBVU82ITd/2TLvJ1K8eIahA==} + engines: {node: '>= 18.0.0'} + '@glimmer/component@2.0.0': resolution: {integrity: sha512-eATSzBOUm0MZ9+YfJx7Y5p3gbwnaeMzLSSsCDn1ihDtUOIm5YYEV0ee0G7tXt/uKxowt8tXYn/EMbI9OlRF0CA==} engines: {node: '>= 18'} @@ -2153,18 +2172,27 @@ packages: '@glimmer/manager@0.92.4': resolution: {integrity: sha512-YMoarZT/+Ft2YSd+Wuu5McVsdP9y6jeAdVQGYFpno3NlL3TXYbl7ELtK7OGxFLjzQE01BdiUZZRvcY+a/s9+CQ==} + '@glimmer/manager@0.94.10': + resolution: {integrity: sha512-Hqi92t6vtVg4nSRGWTvCJ+0Vg3iF1tiTG9RLzuUtZac7DIAzuQAxjhGbtu82miT+liCqU+MFmB3nkfNH0Zz74g==} + '@glimmer/manager@0.94.9': resolution: {integrity: sha512-AQT90eSRbgx6O4VnyRgR+y3SqKChPrpZs5stENa0UnqOSbt7dF6XdqAmllfznKFpLlKmJSV7JaVpCarVTR/JQQ==} '@glimmer/node@0.92.4': resolution: {integrity: sha512-a5GME7HQJZFJPQDdSetQI6jjKXXQi0Vdr3WuUrYwhienVTV5LG0uClbFE2yYWC7TX97YDHpRrNk1CC258rujkQ==} + '@glimmer/node@0.94.10': + resolution: {integrity: sha512-8kw6K+RoKhjfprMO059M7x5yRZRK7WGLzD2056/G+65wV7gnJVDuh4qQirekaagjtskz6OdRBVWrSmrbICWtzQ==} + '@glimmer/node@0.94.9': resolution: {integrity: sha512-X90Xyru/TNi/ocq27ttT4zlMGK931J+pGL0eDYEkUX2fJYHd9Wm1idAB7MLJYIJarv/kuoxteiGThGIYkeNVaQ==} '@glimmer/opcode-compiler@0.92.4': resolution: {integrity: sha512-WnZSBwxNqW/PPD/zfxEg6BVR5tHwTm8fp76piix8BNCQ6CuzVn6HUJ5SlvBsOwyoRCmzt/pkKmBJn+I675KG4w==} + '@glimmer/opcode-compiler@0.94.10': + resolution: {integrity: sha512-KYsaODjkgtpUzMR1chyI0IRcvo4ewnjW8Dy+5833+OIG7rx6INl7HvKtooLzjHv+uJOZ74fd/s/0XfaY6eNEww==} + '@glimmer/opcode-compiler@0.94.9': resolution: {integrity: sha512-LlBniSmtBoIlkxzPKHyOw4Nj946Cczelo8RAnqoG/egkHuk4hoO/7ycSgNpPvV3G14BA4Fpy5ExBffx6iuRxQQ==} @@ -2177,6 +2205,9 @@ packages: '@glimmer/program@0.92.4': resolution: {integrity: sha512-fkquujQ11lsGCWl/+XpZW2E7bjHj/g6/Ht292A7pSoANBD8Bz/gPYiPM+XuMwes9MApEsTEMjV4EXlyk2/Cirg==} + '@glimmer/program@0.94.10': + resolution: {integrity: sha512-a5rpsvBwrcAn0boV4ONy+dHr8tWSTvLAPTR1T1KxF0OBHRVciCAfBPRFemVO6Q3H117At9ifn3uoevtQ6H0M+Q==} + '@glimmer/program@0.94.9': resolution: {integrity: sha512-KA3TXYL2iDdR92pPnB/sw1tgIC7B40l2P60iD1sqkYbyxAbrUPHSToA1ycmK4DwmxDOT3Hz9dvpceoCMbh0xjA==} @@ -2186,12 +2217,18 @@ packages: '@glimmer/reference@0.94.8': resolution: {integrity: sha512-FPoXBRMXJupO9nAq/Vw3EY/FCY3xbd+VALqZupyu6ds9vjNiKAkD9+ujIjYa1f+d/ez2ONhy8QjEFoBsyW2flA==} + '@glimmer/reference@0.94.9': + resolution: {integrity: sha512-qlgTYxgEOpgxuyb13u2qwqhibpfktlk08F+nfwuNxtuhodsItBi3YxjFMPrVP0zOjTnhUObR8OYtMsD5WFOddA==} + '@glimmer/runtime@0.92.4': resolution: {integrity: sha512-ISqM/8hVh+fY/gnLAAPKfts4CvnJBOyCYAXgGccIlzzQrSVLaz0NoRiWTLGj5B/3xyPbqLwYPDvlTsOjYtvPoA==} '@glimmer/runtime@0.94.10': resolution: {integrity: sha512-eRe9TmP02ESVXJn2ZOOEm/Hm/Ro7X0kRvZsU8OVtXOqWU8JxeKMwjCEiLbJBQKbYfycRy1u8jZ2wuH0qM/d3EQ==} + '@glimmer/runtime@0.94.11': + resolution: {integrity: sha512-96PqfxnkEW8k8dMydDmaXgijD7yvtIfjMkHoJ7ljUmE1icZ7jj6f+UIZ0LThpXMzkKaBe1xEapjr91Ldsvmqbg==} + '@glimmer/syntax@0.84.3': resolution: {integrity: sha512-ioVbTic6ZisLxqTgRBL2PCjYZTFIwobifCustrozRU2xGDiYvVIL0vt25h2c1ioDsX59UgVlDkIK4YTAQQSd2A==} @@ -2219,6 +2256,9 @@ packages: '@glimmer/validator@0.94.8': resolution: {integrity: sha512-vTP6hAcrxE5/0dG2w+tHSteXxgWmkBwMzu5ZTxMg+EkqthWl8B5r5skLiviQ6SdKAOBJGhzf6tF4ltHo5y83hQ==} + '@glimmer/validator@0.95.0': + resolution: {integrity: sha512-xF3K5voKeRqhONztfMHDd2wHDYD6UUI9pFPd+RMGtW6DXYv31G0zUm2pGsOwQ9dyNeE6khaXy7e3FtNjDrSmvQ==} + '@glimmer/vm-babel-plugins@0.92.3': resolution: {integrity: sha512-VpkKsHc3oiq9ruiwT7sN4RuOIc5n10PCeWX7tYSNZ85S1bETcAFn0XbyNjI+G3uFshQGEK0T8Fn3+/8VTNIQIg==} engines: {node: '>=16'} @@ -2227,6 +2267,10 @@ packages: resolution: {integrity: sha512-+MjT+U/MsP7O32rXTYlvcmuiKtwI/PflokpVIW0M9wrkfFrsqgdhLQKvA+tNNxFW9LQ55zbhOtJweFNblHOvxg==} engines: {node: '>=18.18.0'} + '@glimmer/vm-babel-plugins@0.93.5': + resolution: {integrity: sha512-xwVRgDjuadOB9qV1jyTKBrUgE/cpmixD/wIYnFf4+hNJRD39urteKRPw98xJSAt7Bw/6y5B8zsgwFS18Nknlrg==} + engines: {node: '>=18.18.0'} + '@glimmer/vm@0.92.3': resolution: {integrity: sha512-DNMQz7nn2zRwKO1irVZ4alg1lH+VInwR3vkWVgobUs0yh7OoHVGXKMd5uxzIksqJEUw1XOX9Qgu/GYZB1PiH3w==} @@ -4428,6 +4472,12 @@ packages: peerDependencies: '@glimmer/component': '>= 1.1.2' + ember-source@6.8.1: + resolution: {integrity: sha512-yGYa7HiXnJA/D0plnS7yCP/1qXGqMocuXi/iJih/QMSLgq3AEy8I5Qaz5re4HOUtpaMh0ZKmyejTD5tLfzhmvQ==} + engines: {node: '>= 18.*'} + peerDependencies: + '@glimmer/component': '>= 1.1.2' + ember-strict-application-resolver@0.1.0: resolution: {integrity: sha512-dmVJPLDoltiB5PN4xhUmg9JrUMPJxdeIlwqUtRBvIHpOLrbi069m7AvXGEh0b+Pq7g1HkLwt/lMm6ouHVry7hQ==} @@ -10596,6 +10646,13 @@ snapshots: '@glimmer/util': 0.94.8 '@glimmer/wire-format': 0.94.8 + '@glimmer/compiler@0.94.11': + dependencies: + '@glimmer/interfaces': 0.94.6 + '@glimmer/syntax': 0.95.0 + '@glimmer/util': 0.94.8 + '@glimmer/wire-format': 0.94.8 + '@glimmer/component@2.0.0': dependencies: '@embroider/addon-shim': 1.10.0 @@ -10662,6 +10719,16 @@ snapshots: '@glimmer/validator': 0.92.3 '@glimmer/vm': 0.92.3 + '@glimmer/manager@0.94.10': + dependencies: + '@glimmer/destroyable': 0.94.8 + '@glimmer/global-context': 0.93.4 + '@glimmer/interfaces': 0.94.6 + '@glimmer/reference': 0.94.9 + '@glimmer/util': 0.94.8 + '@glimmer/validator': 0.95.0 + '@glimmer/vm': 0.94.8 + '@glimmer/manager@0.94.9': dependencies: '@glimmer/destroyable': 0.94.8 @@ -10679,6 +10746,13 @@ snapshots: '@glimmer/util': 0.92.3 '@simple-dom/document': 1.4.0 + '@glimmer/node@0.94.10': + dependencies: + '@glimmer/interfaces': 0.94.6 + '@glimmer/runtime': 0.94.11 + '@glimmer/util': 0.94.8 + '@simple-dom/document': 1.4.0 + '@glimmer/node@0.94.9': dependencies: '@glimmer/interfaces': 0.94.6 @@ -10699,6 +10773,15 @@ snapshots: '@glimmer/vm': 0.92.3 '@glimmer/wire-format': 0.92.3 + '@glimmer/opcode-compiler@0.94.10': + dependencies: + '@glimmer/encoder': 0.93.8 + '@glimmer/interfaces': 0.94.6 + '@glimmer/manager': 0.94.10 + '@glimmer/util': 0.94.8 + '@glimmer/vm': 0.94.8 + '@glimmer/wire-format': 0.94.8 + '@glimmer/opcode-compiler@0.94.9': dependencies: '@glimmer/encoder': 0.93.8 @@ -10725,6 +10808,15 @@ snapshots: '@glimmer/vm': 0.92.3 '@glimmer/wire-format': 0.92.3 + '@glimmer/program@0.94.10': + dependencies: + '@glimmer/interfaces': 0.94.6 + '@glimmer/manager': 0.94.10 + '@glimmer/opcode-compiler': 0.94.10 + '@glimmer/util': 0.94.8 + '@glimmer/vm': 0.94.8 + '@glimmer/wire-format': 0.94.8 + '@glimmer/program@0.94.9': dependencies: '@glimmer/interfaces': 0.94.6 @@ -10749,6 +10841,13 @@ snapshots: '@glimmer/util': 0.94.8 '@glimmer/validator': 0.94.8 + '@glimmer/reference@0.94.9': + dependencies: + '@glimmer/global-context': 0.93.4 + '@glimmer/interfaces': 0.94.6 + '@glimmer/util': 0.94.8 + '@glimmer/validator': 0.95.0 + '@glimmer/runtime@0.92.4': dependencies: '@glimmer/destroyable': 0.92.3 @@ -10777,6 +10876,19 @@ snapshots: '@glimmer/validator': 0.94.8 '@glimmer/vm': 0.94.8 + '@glimmer/runtime@0.94.11': + dependencies: + '@glimmer/destroyable': 0.94.8 + '@glimmer/global-context': 0.93.4 + '@glimmer/interfaces': 0.94.6 + '@glimmer/manager': 0.94.10 + '@glimmer/owner': 0.93.4 + '@glimmer/program': 0.94.10 + '@glimmer/reference': 0.94.9 + '@glimmer/util': 0.94.8 + '@glimmer/validator': 0.95.0 + '@glimmer/vm': 0.94.8 + '@glimmer/syntax@0.84.3': dependencies: '@glimmer/interfaces': 0.84.3 @@ -10835,6 +10947,11 @@ snapshots: '@glimmer/global-context': 0.93.4 '@glimmer/interfaces': 0.94.6 + '@glimmer/validator@0.95.0': + dependencies: + '@glimmer/global-context': 0.93.4 + '@glimmer/interfaces': 0.94.6 + '@glimmer/vm-babel-plugins@0.92.3(@babel/core@7.26.10)': dependencies: babel-plugin-debug-macros: 0.3.4(@babel/core@7.26.10) @@ -10847,6 +10964,12 @@ snapshots: transitivePeerDependencies: - '@babel/core' + '@glimmer/vm-babel-plugins@0.93.5(@babel/core@7.28.4)': + dependencies: + babel-plugin-debug-macros: 0.3.4(@babel/core@7.28.4) + transitivePeerDependencies: + - '@babel/core' + '@glimmer/vm@0.92.3': dependencies: '@glimmer/interfaces': 0.92.3 @@ -13736,6 +13859,52 @@ snapshots: - rsvp - supports-color + ember-source@6.8.1: + dependencies: + '@babel/core': 7.28.4 + '@ember/edition-utils': 1.2.0 + '@embroider/addon-shim': 1.10.0 + '@glimmer/compiler': 0.94.11 + '@glimmer/destroyable': 0.94.8 + '@glimmer/global-context': 0.93.4 + '@glimmer/interfaces': 0.94.6 + '@glimmer/manager': 0.94.10 + '@glimmer/node': 0.94.10 + '@glimmer/opcode-compiler': 0.94.10 + '@glimmer/owner': 0.93.4 + '@glimmer/program': 0.94.10 + '@glimmer/reference': 0.94.9 + '@glimmer/runtime': 0.94.11 + '@glimmer/syntax': 0.95.0 + '@glimmer/util': 0.94.8 + '@glimmer/validator': 0.95.0 + '@glimmer/vm': 0.94.8 + '@glimmer/vm-babel-plugins': 0.93.5(@babel/core@7.28.4) + '@simple-dom/interface': 1.4.0 + backburner.js: 2.8.0 + broccoli-file-creator: 2.1.1 + broccoli-funnel: 3.0.8 + broccoli-merge-trees: 4.2.0 + chalk: 4.1.2 + ember-cli-babel: 8.2.0(@babel/core@7.28.4) + ember-cli-get-component-path-option: 1.0.0 + ember-cli-is-package-missing: 1.0.0 + ember-cli-normalize-entity-name: 1.0.0 + ember-cli-path-utils: 1.0.0 + ember-cli-string-utils: 1.1.0 + ember-cli-typescript-blueprint-polyfill: 0.1.0 + ember-cli-version-checker: 5.1.2 + ember-router-generator: 2.0.0 + inflection: 2.0.1 + route-recognizer: 0.3.4 + router_js: 8.0.6(route-recognizer@0.3.4) + semver: 7.7.3 + silent-error: 1.1.1 + simple-html-tokenizer: 0.5.11 + transitivePeerDependencies: + - rsvp + - supports-color + ember-strict-application-resolver@0.1.0(@babel/core@7.28.4): dependencies: '@embroider/addon-shim': 1.10.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 157ad86b..c66d555c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - packages/* - test-packages/* + - packages/vscode/__fixtures__/ember-app onlyBuiltDependencies: - '@vscode/vsce-sign' - core-js From fcf1487624291a5fc0a61460ce5fae73c80d4855 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:03:42 -0500 Subject: [PATCH 08/27] It works --- bin/build-augmentations.mjs | 2 +- .../-private/dsl/lib.dom.augmentation.d.ts | 2 +- packages/template/-private/dsl/types.d.ts | 2 +- .../template/__tests__/augmentation.test.ts | 26 ++++++++++ .../template/__tests__/custom-element.test.ts | 49 ++++--------------- 5 files changed, 38 insertions(+), 43 deletions(-) create mode 100644 packages/template/__tests__/augmentation.test.ts diff --git a/bin/build-augmentations.mjs b/bin/build-augmentations.mjs index 14cafeb6..84ef786f 100644 --- a/bin/build-augmentations.mjs +++ b/bin/build-augmentations.mjs @@ -54,5 +54,5 @@ let content = prefix; content += '\n'; content += `declare interface GlintElementRegistry {\n`; content += registry.join('\n') + '\n'; -content += `} \n`; +content += `}\n`; writeFileSync(filePath, content); diff --git a/packages/template/-private/dsl/lib.dom.augmentation.d.ts b/packages/template/-private/dsl/lib.dom.augmentation.d.ts index 19289b49..ba8481a4 100644 --- a/packages/template/-private/dsl/lib.dom.augmentation.d.ts +++ b/packages/template/-private/dsl/lib.dom.augmentation.d.ts @@ -136,4 +136,4 @@ declare interface GlintElementRegistry { 'SVGTSpanElement': SVGTSpanElement; 'SVGUseElement': SVGUseElement; 'SVGViewElement': SVGViewElement; -} +} diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index d6ef80a0..6142cd61 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -1,5 +1,5 @@ import './elements'; -import './lib.dom.augmentation' +import './lib.dom.augmentation'; import { AttrValue } from '../index'; // Defined in lib.dom.augmentation.d.ts diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts new file mode 100644 index 00000000..b55a2415 --- /dev/null +++ b/packages/template/__tests__/augmentation.test.ts @@ -0,0 +1,26 @@ +/** + * Tests for these things are used elsewhere, to ensure that we don't have to declaration merge in the file we use custom elements in + */ +import '@glint/template'; + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + +declare global { + interface GlintCustomElements { + 'my-custom-element-emit-element': MyCustomElement; + } + + interface GlintElementRegistry { + MyCustomElement: MyCustomElement; + } + + interface GlintHtmlElementAttributesMap { + MyCustomElement: { + propNum: number; + propStr: string; + }; + } +} diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index cf6387e7..c8181da6 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -1,31 +1,6 @@ -import '@glint/template'; - import { expectTypeOf } from 'expect-type'; import { emitElement, applyAttributes, AttributesForElement, Lookup } from '../-private/dsl'; -class MyCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; -} - - -declare global { - interface GlintCustomElements { - 'my-custom-element-emit-element': MyCustomElement; - } - - interface GlintElementRegistry { - 'MyCustomElement': MyCustomElement; - } - - interface GlintHtmlElementAttributesMap { - 'MyCustomElement': { - propNum: number; - propStr: string; - }; - } -} - /** * Baseline */ @@ -35,10 +10,9 @@ declare global { expectTypeOf().toEqualTypeOf(); applyAttributes(div.element, { - 'data-foo': 123, - 'role': 'button', -}); - + 'data-foo': 123, + role: 'button', + }); } /** @@ -46,26 +20,21 @@ declare global { * (yes) */ { - expectTypeOf().toHaveProperty('MyCustomElement'); expectTypeOf().toHaveProperty('my-custom-element-emit-element'); expectTypeOf().toHaveProperty('MyCustomElement'); - - + const custom = emitElement('my-custom-element-emit-element'); - expectTypeOf(custom).toEqualTypeOf<{ element: MyCustomElement }>(); - expectTypeOf().toEqualTypeOf(); - - type ElementName = Lookup + + type ElementName = Lookup; expectTypeOf().toEqualTypeOf<'MyCustomElement'>(); - + type FoundAttrs = AttributesForElement; expectTypeOf().not.toBeNever(); expectTypeOf().toHaveProperty('propNum'); - + applyAttributes(custom.element, { propNum: 123, propStr: 'hello', - }); + }); } - \ No newline at end of file From a489ef7f04e736c45d26a285ff5c93d24abd4689 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:08:03 -0500 Subject: [PATCH 09/27] Update docs --- docs/glint-types.md | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/docs/glint-types.md b/docs/glint-types.md index 8028289a..449f9396 100644 --- a/docs/glint-types.md +++ b/docs/glint-types.md @@ -210,45 +210,33 @@ declare global { ### Custom Elements / WebComponent types -To add custom elements, you can augment a global interface like so: +To add custom elements, there are 3 global interfaces you must merge with to register the custom element: + - The Invocation Registry, `GlintCustomElements` -- this is how Glint translates element names in HTML into types. + - The Type Registry, `GlintElementRegistry` -- this is how Glint converts the name of your custom element to the actual element type + - The Attribute Registry, `GlintHtmlElementAttributesMap` -- this is how Glint type-checks your props and attributes passed to your custom element. + + + To specify all 3, it would look something like this: + ```ts import '@glint/template'; -import type { MyCustomElementClass } from './wherever.ts'; +import type { MyCustomElementClass, MyCustomElementProps } from './wherever.ts'; declare global { interface GlintCustomElements { - 'my-custom-element': MyCustomElementClass; + 'my-custom-element-emit-element': MyCustomElement; } -} -``` -When doing this, you'll also want your props and attributes to be typed, and that is configured -through a separate declaration, `GlintHtmlElementAttributesMap` + interface GlintElementRegistry { + MyCustomElement: MyCustomElement; + } -```ts -declare global { interface GlintHtmlElementAttributesMap { - 'my-custom-element': { + MyCustomElement: { propNum: number; propStr: string; }; } } -``` - -And they can be combined, if desired: -```ts -import '@glint/template'; - -import type { MyCustomElementClass, MyCustomElementProps } from './wherever.ts'; - -declare global { - interface GlintCustomElements { - 'my-custom-element': MyCustomElementClass; - } - interface GlintHtmlElementAttributesMap { - 'my-custom-element': MyCustomElementProps; - } -} ``` \ No newline at end of file From af97febcb43a1a0695cc62744b22752138ac6128 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:36:31 -0500 Subject: [PATCH 10/27] Revert temporary debugging changes --- .../__fixtures__/ember-app/package.json | 9 +- .../__fixtures__/ember-app/tsconfig.json | 5 +- pnpm-lock.yaml | 169 ------------------ pnpm-workspace.yaml | 1 - 4 files changed, 2 insertions(+), 182 deletions(-) diff --git a/packages/vscode/__fixtures__/ember-app/package.json b/packages/vscode/__fixtures__/ember-app/package.json index 8c914e51..80dea529 100644 --- a/packages/vscode/__fixtures__/ember-app/package.json +++ b/packages/vscode/__fixtures__/ember-app/package.json @@ -1,11 +1,4 @@ { "name": "ember-app", - "private": true, - "devDependencies": { - "@glint/ember-tsc": "workspace:*", - "@glint/template": "workspace:*", - "@glint/tsserver-plugin": "workspace:*", - "ember-source": "6.8.1", - "typescript": "^" - } + "private": true } diff --git a/packages/vscode/__fixtures__/ember-app/tsconfig.json b/packages/vscode/__fixtures__/ember-app/tsconfig.json index 7db53c36..8d5cf2c5 100644 --- a/packages/vscode/__fixtures__/ember-app/tsconfig.json +++ b/packages/vscode/__fixtures__/ember-app/tsconfig.json @@ -1,9 +1,6 @@ { "extends": "../../../../tsconfig.compileroptions.json", "compilerOptions": { - "moduleResolution": "bundler", - "module": "esnext", - "baseUrl": ".", - "types": ["ember-source/types"] + "baseUrl": "." } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a954b09..be3e596a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,21 +321,6 @@ importers: specifier: ^0.5.0 version: 0.5.5 - packages/vscode/__fixtures__/ember-app: - devDependencies: - '@glint/ember-tsc': - specifier: workspace:* - version: link:../../../core - '@glint/template': - specifier: workspace:* - version: link:../../../template - '@glint/tsserver-plugin': - specifier: workspace:* - version: link:../../../tsserver-plugin - ember-source: - specifier: 6.8.1 - version: 6.8.1 - test-packages/package-test-core: devDependencies: '@glimmer/component': @@ -2128,10 +2113,6 @@ packages: resolution: {integrity: sha512-SrWiaKM3AND2FQ732wtjAKol7XhCnRqit3tJShG4X0mT27Jb3zuhTI2dkfYVVMTJ23pjT/+0y+s/uGaBSirnBg==} engines: {node: '>= 18.0.0'} - '@glimmer/compiler@0.94.11': - resolution: {integrity: sha512-t9eyLZIFsiwAib8Zyfu67yBep5Vn2bd5DScIE2hharPE/OKKI7cpQYi6BzQhSGYEBVU82ITd/2TLvJ1K8eIahA==} - engines: {node: '>= 18.0.0'} - '@glimmer/component@2.0.0': resolution: {integrity: sha512-eATSzBOUm0MZ9+YfJx7Y5p3gbwnaeMzLSSsCDn1ihDtUOIm5YYEV0ee0G7tXt/uKxowt8tXYn/EMbI9OlRF0CA==} engines: {node: '>= 18'} @@ -2172,27 +2153,18 @@ packages: '@glimmer/manager@0.92.4': resolution: {integrity: sha512-YMoarZT/+Ft2YSd+Wuu5McVsdP9y6jeAdVQGYFpno3NlL3TXYbl7ELtK7OGxFLjzQE01BdiUZZRvcY+a/s9+CQ==} - '@glimmer/manager@0.94.10': - resolution: {integrity: sha512-Hqi92t6vtVg4nSRGWTvCJ+0Vg3iF1tiTG9RLzuUtZac7DIAzuQAxjhGbtu82miT+liCqU+MFmB3nkfNH0Zz74g==} - '@glimmer/manager@0.94.9': resolution: {integrity: sha512-AQT90eSRbgx6O4VnyRgR+y3SqKChPrpZs5stENa0UnqOSbt7dF6XdqAmllfznKFpLlKmJSV7JaVpCarVTR/JQQ==} '@glimmer/node@0.92.4': resolution: {integrity: sha512-a5GME7HQJZFJPQDdSetQI6jjKXXQi0Vdr3WuUrYwhienVTV5LG0uClbFE2yYWC7TX97YDHpRrNk1CC258rujkQ==} - '@glimmer/node@0.94.10': - resolution: {integrity: sha512-8kw6K+RoKhjfprMO059M7x5yRZRK7WGLzD2056/G+65wV7gnJVDuh4qQirekaagjtskz6OdRBVWrSmrbICWtzQ==} - '@glimmer/node@0.94.9': resolution: {integrity: sha512-X90Xyru/TNi/ocq27ttT4zlMGK931J+pGL0eDYEkUX2fJYHd9Wm1idAB7MLJYIJarv/kuoxteiGThGIYkeNVaQ==} '@glimmer/opcode-compiler@0.92.4': resolution: {integrity: sha512-WnZSBwxNqW/PPD/zfxEg6BVR5tHwTm8fp76piix8BNCQ6CuzVn6HUJ5SlvBsOwyoRCmzt/pkKmBJn+I675KG4w==} - '@glimmer/opcode-compiler@0.94.10': - resolution: {integrity: sha512-KYsaODjkgtpUzMR1chyI0IRcvo4ewnjW8Dy+5833+OIG7rx6INl7HvKtooLzjHv+uJOZ74fd/s/0XfaY6eNEww==} - '@glimmer/opcode-compiler@0.94.9': resolution: {integrity: sha512-LlBniSmtBoIlkxzPKHyOw4Nj946Cczelo8RAnqoG/egkHuk4hoO/7ycSgNpPvV3G14BA4Fpy5ExBffx6iuRxQQ==} @@ -2205,9 +2177,6 @@ packages: '@glimmer/program@0.92.4': resolution: {integrity: sha512-fkquujQ11lsGCWl/+XpZW2E7bjHj/g6/Ht292A7pSoANBD8Bz/gPYiPM+XuMwes9MApEsTEMjV4EXlyk2/Cirg==} - '@glimmer/program@0.94.10': - resolution: {integrity: sha512-a5rpsvBwrcAn0boV4ONy+dHr8tWSTvLAPTR1T1KxF0OBHRVciCAfBPRFemVO6Q3H117At9ifn3uoevtQ6H0M+Q==} - '@glimmer/program@0.94.9': resolution: {integrity: sha512-KA3TXYL2iDdR92pPnB/sw1tgIC7B40l2P60iD1sqkYbyxAbrUPHSToA1ycmK4DwmxDOT3Hz9dvpceoCMbh0xjA==} @@ -2217,18 +2186,12 @@ packages: '@glimmer/reference@0.94.8': resolution: {integrity: sha512-FPoXBRMXJupO9nAq/Vw3EY/FCY3xbd+VALqZupyu6ds9vjNiKAkD9+ujIjYa1f+d/ez2ONhy8QjEFoBsyW2flA==} - '@glimmer/reference@0.94.9': - resolution: {integrity: sha512-qlgTYxgEOpgxuyb13u2qwqhibpfktlk08F+nfwuNxtuhodsItBi3YxjFMPrVP0zOjTnhUObR8OYtMsD5WFOddA==} - '@glimmer/runtime@0.92.4': resolution: {integrity: sha512-ISqM/8hVh+fY/gnLAAPKfts4CvnJBOyCYAXgGccIlzzQrSVLaz0NoRiWTLGj5B/3xyPbqLwYPDvlTsOjYtvPoA==} '@glimmer/runtime@0.94.10': resolution: {integrity: sha512-eRe9TmP02ESVXJn2ZOOEm/Hm/Ro7X0kRvZsU8OVtXOqWU8JxeKMwjCEiLbJBQKbYfycRy1u8jZ2wuH0qM/d3EQ==} - '@glimmer/runtime@0.94.11': - resolution: {integrity: sha512-96PqfxnkEW8k8dMydDmaXgijD7yvtIfjMkHoJ7ljUmE1icZ7jj6f+UIZ0LThpXMzkKaBe1xEapjr91Ldsvmqbg==} - '@glimmer/syntax@0.84.3': resolution: {integrity: sha512-ioVbTic6ZisLxqTgRBL2PCjYZTFIwobifCustrozRU2xGDiYvVIL0vt25h2c1ioDsX59UgVlDkIK4YTAQQSd2A==} @@ -2256,9 +2219,6 @@ packages: '@glimmer/validator@0.94.8': resolution: {integrity: sha512-vTP6hAcrxE5/0dG2w+tHSteXxgWmkBwMzu5ZTxMg+EkqthWl8B5r5skLiviQ6SdKAOBJGhzf6tF4ltHo5y83hQ==} - '@glimmer/validator@0.95.0': - resolution: {integrity: sha512-xF3K5voKeRqhONztfMHDd2wHDYD6UUI9pFPd+RMGtW6DXYv31G0zUm2pGsOwQ9dyNeE6khaXy7e3FtNjDrSmvQ==} - '@glimmer/vm-babel-plugins@0.92.3': resolution: {integrity: sha512-VpkKsHc3oiq9ruiwT7sN4RuOIc5n10PCeWX7tYSNZ85S1bETcAFn0XbyNjI+G3uFshQGEK0T8Fn3+/8VTNIQIg==} engines: {node: '>=16'} @@ -2267,10 +2227,6 @@ packages: resolution: {integrity: sha512-+MjT+U/MsP7O32rXTYlvcmuiKtwI/PflokpVIW0M9wrkfFrsqgdhLQKvA+tNNxFW9LQ55zbhOtJweFNblHOvxg==} engines: {node: '>=18.18.0'} - '@glimmer/vm-babel-plugins@0.93.5': - resolution: {integrity: sha512-xwVRgDjuadOB9qV1jyTKBrUgE/cpmixD/wIYnFf4+hNJRD39urteKRPw98xJSAt7Bw/6y5B8zsgwFS18Nknlrg==} - engines: {node: '>=18.18.0'} - '@glimmer/vm@0.92.3': resolution: {integrity: sha512-DNMQz7nn2zRwKO1irVZ4alg1lH+VInwR3vkWVgobUs0yh7OoHVGXKMd5uxzIksqJEUw1XOX9Qgu/GYZB1PiH3w==} @@ -4472,12 +4428,6 @@ packages: peerDependencies: '@glimmer/component': '>= 1.1.2' - ember-source@6.8.1: - resolution: {integrity: sha512-yGYa7HiXnJA/D0plnS7yCP/1qXGqMocuXi/iJih/QMSLgq3AEy8I5Qaz5re4HOUtpaMh0ZKmyejTD5tLfzhmvQ==} - engines: {node: '>= 18.*'} - peerDependencies: - '@glimmer/component': '>= 1.1.2' - ember-strict-application-resolver@0.1.0: resolution: {integrity: sha512-dmVJPLDoltiB5PN4xhUmg9JrUMPJxdeIlwqUtRBvIHpOLrbi069m7AvXGEh0b+Pq7g1HkLwt/lMm6ouHVry7hQ==} @@ -10646,13 +10596,6 @@ snapshots: '@glimmer/util': 0.94.8 '@glimmer/wire-format': 0.94.8 - '@glimmer/compiler@0.94.11': - dependencies: - '@glimmer/interfaces': 0.94.6 - '@glimmer/syntax': 0.95.0 - '@glimmer/util': 0.94.8 - '@glimmer/wire-format': 0.94.8 - '@glimmer/component@2.0.0': dependencies: '@embroider/addon-shim': 1.10.0 @@ -10719,16 +10662,6 @@ snapshots: '@glimmer/validator': 0.92.3 '@glimmer/vm': 0.92.3 - '@glimmer/manager@0.94.10': - dependencies: - '@glimmer/destroyable': 0.94.8 - '@glimmer/global-context': 0.93.4 - '@glimmer/interfaces': 0.94.6 - '@glimmer/reference': 0.94.9 - '@glimmer/util': 0.94.8 - '@glimmer/validator': 0.95.0 - '@glimmer/vm': 0.94.8 - '@glimmer/manager@0.94.9': dependencies: '@glimmer/destroyable': 0.94.8 @@ -10746,13 +10679,6 @@ snapshots: '@glimmer/util': 0.92.3 '@simple-dom/document': 1.4.0 - '@glimmer/node@0.94.10': - dependencies: - '@glimmer/interfaces': 0.94.6 - '@glimmer/runtime': 0.94.11 - '@glimmer/util': 0.94.8 - '@simple-dom/document': 1.4.0 - '@glimmer/node@0.94.9': dependencies: '@glimmer/interfaces': 0.94.6 @@ -10773,15 +10699,6 @@ snapshots: '@glimmer/vm': 0.92.3 '@glimmer/wire-format': 0.92.3 - '@glimmer/opcode-compiler@0.94.10': - dependencies: - '@glimmer/encoder': 0.93.8 - '@glimmer/interfaces': 0.94.6 - '@glimmer/manager': 0.94.10 - '@glimmer/util': 0.94.8 - '@glimmer/vm': 0.94.8 - '@glimmer/wire-format': 0.94.8 - '@glimmer/opcode-compiler@0.94.9': dependencies: '@glimmer/encoder': 0.93.8 @@ -10808,15 +10725,6 @@ snapshots: '@glimmer/vm': 0.92.3 '@glimmer/wire-format': 0.92.3 - '@glimmer/program@0.94.10': - dependencies: - '@glimmer/interfaces': 0.94.6 - '@glimmer/manager': 0.94.10 - '@glimmer/opcode-compiler': 0.94.10 - '@glimmer/util': 0.94.8 - '@glimmer/vm': 0.94.8 - '@glimmer/wire-format': 0.94.8 - '@glimmer/program@0.94.9': dependencies: '@glimmer/interfaces': 0.94.6 @@ -10841,13 +10749,6 @@ snapshots: '@glimmer/util': 0.94.8 '@glimmer/validator': 0.94.8 - '@glimmer/reference@0.94.9': - dependencies: - '@glimmer/global-context': 0.93.4 - '@glimmer/interfaces': 0.94.6 - '@glimmer/util': 0.94.8 - '@glimmer/validator': 0.95.0 - '@glimmer/runtime@0.92.4': dependencies: '@glimmer/destroyable': 0.92.3 @@ -10876,19 +10777,6 @@ snapshots: '@glimmer/validator': 0.94.8 '@glimmer/vm': 0.94.8 - '@glimmer/runtime@0.94.11': - dependencies: - '@glimmer/destroyable': 0.94.8 - '@glimmer/global-context': 0.93.4 - '@glimmer/interfaces': 0.94.6 - '@glimmer/manager': 0.94.10 - '@glimmer/owner': 0.93.4 - '@glimmer/program': 0.94.10 - '@glimmer/reference': 0.94.9 - '@glimmer/util': 0.94.8 - '@glimmer/validator': 0.95.0 - '@glimmer/vm': 0.94.8 - '@glimmer/syntax@0.84.3': dependencies: '@glimmer/interfaces': 0.84.3 @@ -10947,11 +10835,6 @@ snapshots: '@glimmer/global-context': 0.93.4 '@glimmer/interfaces': 0.94.6 - '@glimmer/validator@0.95.0': - dependencies: - '@glimmer/global-context': 0.93.4 - '@glimmer/interfaces': 0.94.6 - '@glimmer/vm-babel-plugins@0.92.3(@babel/core@7.26.10)': dependencies: babel-plugin-debug-macros: 0.3.4(@babel/core@7.26.10) @@ -10964,12 +10847,6 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@glimmer/vm-babel-plugins@0.93.5(@babel/core@7.28.4)': - dependencies: - babel-plugin-debug-macros: 0.3.4(@babel/core@7.28.4) - transitivePeerDependencies: - - '@babel/core' - '@glimmer/vm@0.92.3': dependencies: '@glimmer/interfaces': 0.92.3 @@ -13859,52 +13736,6 @@ snapshots: - rsvp - supports-color - ember-source@6.8.1: - dependencies: - '@babel/core': 7.28.4 - '@ember/edition-utils': 1.2.0 - '@embroider/addon-shim': 1.10.0 - '@glimmer/compiler': 0.94.11 - '@glimmer/destroyable': 0.94.8 - '@glimmer/global-context': 0.93.4 - '@glimmer/interfaces': 0.94.6 - '@glimmer/manager': 0.94.10 - '@glimmer/node': 0.94.10 - '@glimmer/opcode-compiler': 0.94.10 - '@glimmer/owner': 0.93.4 - '@glimmer/program': 0.94.10 - '@glimmer/reference': 0.94.9 - '@glimmer/runtime': 0.94.11 - '@glimmer/syntax': 0.95.0 - '@glimmer/util': 0.94.8 - '@glimmer/validator': 0.95.0 - '@glimmer/vm': 0.94.8 - '@glimmer/vm-babel-plugins': 0.93.5(@babel/core@7.28.4) - '@simple-dom/interface': 1.4.0 - backburner.js: 2.8.0 - broccoli-file-creator: 2.1.1 - broccoli-funnel: 3.0.8 - broccoli-merge-trees: 4.2.0 - chalk: 4.1.2 - ember-cli-babel: 8.2.0(@babel/core@7.28.4) - ember-cli-get-component-path-option: 1.0.0 - ember-cli-is-package-missing: 1.0.0 - ember-cli-normalize-entity-name: 1.0.0 - ember-cli-path-utils: 1.0.0 - ember-cli-string-utils: 1.1.0 - ember-cli-typescript-blueprint-polyfill: 0.1.0 - ember-cli-version-checker: 5.1.2 - ember-router-generator: 2.0.0 - inflection: 2.0.1 - route-recognizer: 0.3.4 - router_js: 8.0.6(route-recognizer@0.3.4) - semver: 7.7.3 - silent-error: 1.1.1 - simple-html-tokenizer: 0.5.11 - transitivePeerDependencies: - - rsvp - - supports-color - ember-strict-application-resolver@0.1.0(@babel/core@7.28.4): dependencies: '@embroider/addon-shim': 1.10.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c66d555c..157ad86b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,6 @@ packages: - packages/* - test-packages/* - - packages/vscode/__fixtures__/ember-app onlyBuiltDependencies: - '@vscode/vsce-sign' - core-js From cd331d8ca4e63565e1758ece6696729da60da012 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:36:46 -0500 Subject: [PATCH 11/27] Reduce changes --- .vscode/launch.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0d45b283..519f172c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,7 +22,6 @@ // comment to activate your local extensions "--disable-extensions", "${workspaceFolder}/packages/vscode/__fixtures__/ember-app" - ] }, { From b5f5d430f39101e20b419f3dafe836790e25a00a Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:41:49 -0500 Subject: [PATCH 12/27] Hm, still doesn't work in an actual gts file --- .vscode/launch.json | 27 +++++++++++++++-- package.json | 1 + pnpm-lock.yaml | 3 ++ .../src/custom-elements.gts | 29 ++++++++++--------- .../ts-template-imports-app/types/index.d.ts | 22 ++++++++++++++ 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 519f172c..62566f60 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,10 +1,10 @@ { "version": "0.2.0", "configurations": [ - { + { // For this to work, make sure you're running `tsc --build --watch` at the root, AND // `pnpm bundle:watch` from within vscode directory. - "name": "Debug Extension (TS Plugin, .gts)", + "name": "Debug Extension (TS Plugin, .gts, ember-app)", "type": "extensionHost", "request": "launch", // "preLaunchTask": "npm: build", @@ -24,6 +24,29 @@ "${workspaceFolder}/packages/vscode/__fixtures__/ember-app" ] }, + { + // For this to work, make sure you're running `tsc --build --watch` at the root, AND + // `pnpm bundle:watch` from within vscode directory. + "name": "Debug Extension (TS Plugin, .gts, ts-template-imports-app)", + "type": "extensionHost", + "request": "launch", + // "preLaunchTask": "npm: build", + "autoAttachChildProcesses": true, + "runtimeExecutable": "${execPath}", + "outFiles": [ + "${workspaceFolder}/**/*.js", + "!**/node_modules/**" + ], + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/packages/vscode", + // Disable v1 vscode glint + "--disable-extension", + "typed-ember.glint-vscode", + // comment to activate your local extensions + // "--disable-extensions", + "${workspaceFolder}/test-packages/ts-template-imports-app" + ] + }, { "name": "Attach to TS Server", "type": "node", diff --git a/package.json b/package.json index be95b9c7..0b2a03dd 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@babel/core": "^7.26.10", "@babel/parser": "^7.27.0", "@glimmer/component": "^2.0.0", + "@glint/ember-tsc": "workspace:*", "@glint/tsserver-plugin": "workspace:*", "@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/parser": "^5.42.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be3e596a..9d28ecca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@glimmer/component': specifier: ^2.0.0 version: 2.0.0 + '@glint/ember-tsc': + specifier: workspace:* + version: link:packages/core '@glint/tsserver-plugin': specifier: workspace:* version: link:packages/tsserver-plugin diff --git a/test-packages/ts-template-imports-app/src/custom-elements.gts b/test-packages/ts-template-imports-app/src/custom-elements.gts index 4eafdc72..1b05197d 100644 --- a/test-packages/ts-template-imports-app/src/custom-elements.gts +++ b/test-packages/ts-template-imports-app/src/custom-elements.gts @@ -1,20 +1,21 @@ - -import '@glint/template'; -declare global { - interface GlintHtmlElementAttributesMap { - 'my-custom-element': { - propNum: number; - propStr: string; - }; - } -} - const two = 2; const str = "hello"; +type X = GlintCustomElements['my-custom-element']; + export const UsesCustomElement = ; \ No newline at end of file + + +; diff --git a/test-packages/ts-template-imports-app/types/index.d.ts b/test-packages/ts-template-imports-app/types/index.d.ts index 1ce64102..42e92b1d 100644 --- a/test-packages/ts-template-imports-app/types/index.d.ts +++ b/test-packages/ts-template-imports-app/types/index.d.ts @@ -13,3 +13,25 @@ declare module '@glint/ember-tsc/globals' { }>; } } + +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + +declare global { + interface GlintCustomElements { + 'my-custom-element': MyCustomElement; + } + + interface GlintElementRegistry { + MyCustomElement: MyCustomElement; + } + + interface GlintHtmlElementAttributesMap { + 'my-custom-element': { + propNum: number; + propStr: string; + }; + } +} From 1c9b1a72c5e0f96621d77108174cb877549b87bc Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:26:19 -0500 Subject: [PATCH 13/27] Update tests --- bin/build-augmentations.mjs | 3 +- bin/build-elements.mjs | 4 +++ packages/template/-private/dsl/elements.d.ts | 4 +++ .../-private/dsl/lib.dom.augmentation.d.ts | 5 +-- packages/template/-private/dsl/types.d.ts | 36 ++++++++++++------- .../template/__tests__/augmentation.test.ts | 7 ++-- .../template/__tests__/custom-element.test.ts | 26 +++++++++++--- .../src/custom-element/augmented.gts | 34 ++++++++++++++++++ .../src/custom-element/custom-elements.gts | 15 ++++++++ 9 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts create mode 100644 packages/vscode/__fixtures__/ember-app/src/custom-element/custom-elements.gts diff --git a/bin/build-augmentations.mjs b/bin/build-augmentations.mjs index 84ef786f..fd8eeb28 100644 --- a/bin/build-augmentations.mjs +++ b/bin/build-augmentations.mjs @@ -44,7 +44,6 @@ const prefix = `// generated by /bin/build-augmentations.mjs // The TypeScript lib.dom.d.ts already has HTMLElementTagNameMap, // but it does not provide unique types for each element, and the technique // we use for looking up the type-string for each element does not work with the built in types. - `; const filePath = resolve( fileURLToPath(import.meta.url), @@ -52,7 +51,7 @@ const filePath = resolve( ); let content = prefix; content += '\n'; -content += `declare interface GlintElementRegistry {\n`; +content += `export interface GlintElementRegistry {\n`; content += registry.join('\n') + '\n'; content += `}\n`; writeFileSync(filePath, content); diff --git a/bin/build-elements.mjs b/bin/build-elements.mjs index 6ea0bee6..4a113662 100644 --- a/bin/build-elements.mjs +++ b/bin/build-elements.mjs @@ -85,6 +85,10 @@ function createHtmlElementsAttributesMap() { import { AttrValue } from '../index'; declare global { + interface GlintCustomElementRegistry { + // e.g.: + // 'my-button': MyButtonElement; + } `; const processed = new Set(); diff --git a/packages/template/-private/dsl/elements.d.ts b/packages/template/-private/dsl/elements.d.ts index 731db1d9..302c9a80 100644 --- a/packages/template/-private/dsl/elements.d.ts +++ b/packages/template/-private/dsl/elements.d.ts @@ -4,6 +4,10 @@ import { AttrValue } from '../index'; declare global { + interface GlintCustomElementRegistry { + // e.g.: + // 'my-button': MyButtonElement; + } interface GlobalAriaAttributes { ['aria-activedescendant']: AttrValue; ['aria-atomic']: AttrValue; diff --git a/packages/template/-private/dsl/lib.dom.augmentation.d.ts b/packages/template/-private/dsl/lib.dom.augmentation.d.ts index ba8481a4..bd0201bd 100644 --- a/packages/template/-private/dsl/lib.dom.augmentation.d.ts +++ b/packages/template/-private/dsl/lib.dom.augmentation.d.ts @@ -7,8 +7,8 @@ // but it does not provide unique types for each element, and the technique // we use for looking up the type-string for each element does not work with the built in types. - -declare interface GlintElementRegistry { +declare global { +interface GlintElementRegistry { 'SVGElement': SVGElement; 'HTMLAnchorElement': HTMLAnchorElement; 'HTMLElement': HTMLElement; @@ -137,3 +137,4 @@ declare interface GlintElementRegistry { 'SVGUseElement': SVGUseElement; 'SVGViewElement': SVGViewElement; } +} \ No newline at end of file diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 6142cd61..e0e15572 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -1,9 +1,6 @@ import './elements'; -import './lib.dom.augmentation'; import { AttrValue } from '../index'; - -// Defined in lib.dom.augmentation.d.ts -type Registry = GlintElementRegistry; +import { GlintElementRegistry } from './lib.dom.augmentation'; /** * This doesn't generate _totally_ unique mappings, but they all have the same attributes. @@ -17,13 +14,24 @@ type Registry = GlintElementRegistry; * * And for the purposes of attribute lookup, that's good enough. */ -type Lookup = { - [K in keyof Registry]: [Registry[K]] extends [T] // check assignability in one direction - ? [T] extends [Registry[K]] // and in the other +export type Lookup = { + [K in keyof GlintElementRegistry]: [GlintElementRegistry[K]] extends [T] // check assignability in one direction + ? [T] extends [GlintElementRegistry[K]] // and in the other + ? K // if both true, exact match + : never + : never; +}[keyof GlintElementRegistry]; + +/** + * Same thing, but for CustomElements + */ +export type CustomElementLookup = { + [K in keyof GlintCustomElementRegistry]: [GlintCustomElementRegistry[K]] extends [T] // check assignability in one direction + ? [T] extends [GlintCustomElementRegistry[K]] // and in the other ? K // if both true, exact match : never : never; -}[keyof Registry]; +}[keyof GlintCustomElementRegistry]; /** * A utility for constructing the type of an environment's `resolveOrReturn` from @@ -37,8 +45,8 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - : Name extends keyof GlintCustomElements - ? GlintCustomElements[Name] + : Name extends keyof GlintCustomElementRegistry + ? GlintCustomElementRegistry[Name] : Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap @@ -57,6 +65,8 @@ export type AttributesForElement> = : // Or is K in the SVG attributes map? K extends keyof GlintSvgElementAttributesMap ? WithDataAttributes - : // If the element can't be found: fallback to just allow general AttrValue - // NOTE: MathML has no attributes - Record; + : K extends keyof GlintCustomElementRegistry + ? WithDataAttributes + : // If the element can't be found: fallback to just allow general AttrValue + // NOTE: MathML has no attributes + Record; diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index b55a2415..7badd890 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -9,12 +9,9 @@ class MyCustomElement extends HTMLElement { } declare global { - interface GlintCustomElements { - 'my-custom-element-emit-element': MyCustomElement; - } - - interface GlintElementRegistry { + interface GlintCustomElementRegistry { MyCustomElement: MyCustomElement; + 'my-custom-element-emit-element': MyCustomElement; } interface GlintHtmlElementAttributesMap { diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index c8181da6..99f0f5e9 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -1,5 +1,11 @@ import { expectTypeOf } from 'expect-type'; -import { emitElement, applyAttributes, AttributesForElement, Lookup } from '../-private/dsl'; +import { + emitElement, + applyAttributes, + AttributesForElement, + CustomElementLookup, +} from '../-private/dsl'; +import { AttrValue } from '../-private'; /** * Baseline @@ -20,21 +26,31 @@ import { emitElement, applyAttributes, AttributesForElement, Lookup } from '../- * (yes) */ { - expectTypeOf().toHaveProperty('MyCustomElement'); - expectTypeOf().toHaveProperty('my-custom-element-emit-element'); + expectTypeOf().toHaveProperty('MyCustomElement'); + expectTypeOf().toHaveProperty('my-custom-element-emit-element'); expectTypeOf().toHaveProperty('MyCustomElement'); const custom = emitElement('my-custom-element-emit-element'); - type ElementName = Lookup; - expectTypeOf().toEqualTypeOf<'MyCustomElement'>(); + type ElementName = CustomElementLookup; + expectTypeOf().toEqualTypeOf<'MyCustomElement' | 'my-custom-element-emit-element'>(); type FoundAttrs = AttributesForElement; expectTypeOf().not.toBeNever(); expectTypeOf().toHaveProperty('propNum'); + // The default type for attributes + expectTypeOf().toEqualTypeOf(); + expectTypeOf().not.toEqualTypeOf(); applyAttributes(custom.element, { propNum: 123, propStr: 'hello', }); + + applyAttributes(custom.element, { + // @ts-expect-error propNum expects a number, and I gave it a string to test that an error occurs + propNum: 'wrong', + // @ts-expect-error propStr expects a string, and I gave it a number to test that an error occurs + propStr: 123, + }); } diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts b/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts new file mode 100644 index 00000000..5e4c8f62 --- /dev/null +++ b/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts @@ -0,0 +1,34 @@ +import '@glint/template'; + +declare global { + interface HTMLStyleElementAttributes { + scoped: ''; + inline: ''; + } +} +class MyCustomElement extends HTMLElement { + propNum!: number; + propStr!: string; +} + +declare global { + interface GlintCustomElementRegistry { + MyCustomElement: MyCustomElement; + 'my-custom-element': MyCustomElement; + } + + interface GlintHtmlElementAttributesMap { + MyCustomElement: { + propNum: number; + propStr: string; + }; + } +} + + + \ No newline at end of file diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-element/custom-elements.gts b/packages/vscode/__fixtures__/ember-app/src/custom-element/custom-elements.gts new file mode 100644 index 00000000..e363a631 --- /dev/null +++ b/packages/vscode/__fixtures__/ember-app/src/custom-element/custom-elements.gts @@ -0,0 +1,15 @@ +const two = 2; +const str = "hello"; + +export type X = GlintCustomElementRegistry['my-custom-element']; + +export const UsesCustomElement = ; From 6e240596c641deaed0e6096d636eb0bab90767bc Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:40:09 -0500 Subject: [PATCH 14/27] I think I need to abandon some of this and make a emitCustomElement --- .../-private/dsl/custom-elements.d.ts | 4 ++-- packages/template/-private/dsl/types.d.ts | 8 ++++---- .../template/__tests__/attributes.test.ts | 8 ++++---- .../template/__tests__/augmentation.test.ts | 20 ++++++++++--------- .../template/__tests__/custom-element.test.ts | 14 ++++++------- .../private/ElementForTagName.test.ts | 2 +- .../src/custom-element/augmented.gts | 12 +++++------ .../ts-template-imports-app/types/index.d.ts | 12 +++++------ 8 files changed, 41 insertions(+), 39 deletions(-) diff --git a/packages/template/-private/dsl/custom-elements.d.ts b/packages/template/-private/dsl/custom-elements.d.ts index 8ce3960e..5572a4bb 100644 --- a/packages/template/-private/dsl/custom-elements.d.ts +++ b/packages/template/-private/dsl/custom-elements.d.ts @@ -6,7 +6,7 @@ declare global { * augment it in your own project like so: * ```ts * declare global { - * interface GlintCustomElements { + * interface GlintCustomElementRegistry { * 'my-custom-element': MyCustomElementClass; * } * } @@ -27,7 +27,7 @@ declare global { * } * ``` */ - interface GlintCustomElements { + interface GlintCustomElementRegistry { /* intentionally empty, as there are no custom elements by default */ } } diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index e0e15572..15afd876 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -45,8 +45,8 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - : Name extends keyof GlintCustomElementRegistry - ? GlintCustomElementRegistry[Name] + // : Name extends keyof GlintCustomElementRegistry + // ? GlintCustomElementRegistry[Name] : Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap @@ -65,8 +65,8 @@ export type AttributesForElement> = : // Or is K in the SVG attributes map? K extends keyof GlintSvgElementAttributesMap ? WithDataAttributes - : K extends keyof GlintCustomElementRegistry - ? WithDataAttributes + // : K extends keyof GlintCustomElementRegistry + // ? WithDataAttributes : // If the element can't be found: fallback to just allow general AttrValue // NOTE: MathML has no attributes Record; diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index 4e255182..31c94bcf 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -69,13 +69,13 @@ interface RegisteredCustomElementAttributes { } declare global { - interface GlintCustomElements { + interface GlintCustomElementRegistry { 'registered-custom-element': RegisteredCustomElement; } - interface GlintHtmlElementAttributesMap { - 'registered-custom-element': RegisteredCustomElementAttributes; - } + // interface GlintHtmlElementAttributesMap { + // 'registered-custom-element': RegisteredCustomElementAttributes; + // } } { diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index 7badd890..374ca09d 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -3,21 +3,23 @@ */ import '@glint/template'; -class MyCustomElement extends HTMLElement { + +class AugmentedCustomElement extends HTMLElement { propNum!: number; propStr!: string; + declare static readonly __brand: unique symbol; } declare global { interface GlintCustomElementRegistry { - MyCustomElement: MyCustomElement; - 'my-custom-element-emit-element': MyCustomElement; + AugmentedCustomElement: AugmentedCustomElement; + 'augmented-custom-element': AugmentedCustomElement; } - interface GlintHtmlElementAttributesMap { - MyCustomElement: { - propNum: number; - propStr: string; - }; - } + // interface GlintHtmlElementAttributesMap { + // AugmentedCustomElement: { + // propNum: number; + // propStr: string; + // }; + // } } diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 99f0f5e9..d45e49a1 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -26,21 +26,21 @@ import { AttrValue } from '../-private'; * (yes) */ { - expectTypeOf().toHaveProperty('MyCustomElement'); - expectTypeOf().toHaveProperty('my-custom-element-emit-element'); - expectTypeOf().toHaveProperty('MyCustomElement'); + expectTypeOf().toHaveProperty('AugmentedCustomElement'); + expectTypeOf().toHaveProperty('augmented-custom-element'); + expectTypeOf().toHaveProperty('AugmentedCustomElement'); - const custom = emitElement('my-custom-element-emit-element'); + const custom = emitElement('augmented-custom-element'); type ElementName = CustomElementLookup; - expectTypeOf().toEqualTypeOf<'MyCustomElement' | 'my-custom-element-emit-element'>(); + expectTypeOf().not.toBeNever(); + expectTypeOf().toEqualTypeOf<'AugmentedCustomElement' | 'augmented-custom-element' | 'my-custom-element-element-for-tag-name'>(); type FoundAttrs = AttributesForElement; expectTypeOf().not.toBeNever(); expectTypeOf().toHaveProperty('propNum'); // The default type for attributes - expectTypeOf().toEqualTypeOf(); - expectTypeOf().not.toEqualTypeOf(); + expectTypeOf().not.toEqualTypeOf(); applyAttributes(custom.element, { propNum: 123, diff --git a/packages/template/__tests__/private/ElementForTagName.test.ts b/packages/template/__tests__/private/ElementForTagName.test.ts index e93ec296..f82152b6 100644 --- a/packages/template/__tests__/private/ElementForTagName.test.ts +++ b/packages/template/__tests__/private/ElementForTagName.test.ts @@ -9,7 +9,7 @@ class MyCustomElement extends HTMLElement { } declare global { - interface GlintCustomElements { + interface GlintCustomElementRegistry { 'my-custom-element-element-for-tag-name': MyCustomElement; } } diff --git a/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts b/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts index 5e4c8f62..33fc16a8 100644 --- a/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts +++ b/packages/vscode/__fixtures__/ember-app/src/custom-element/augmented.gts @@ -17,12 +17,12 @@ declare global { 'my-custom-element': MyCustomElement; } - interface GlintHtmlElementAttributesMap { - MyCustomElement: { - propNum: number; - propStr: string; - }; - } + // interface GlintHtmlElementAttributesMap { + // MyCustomElement: { + // propNum: number; + // propStr: string; + // }; + // } } diff --git a/test-packages/ts-template-imports-app/types/index.d.ts b/test-packages/ts-template-imports-app/types/index.d.ts index 42e92b1d..032b2865 100644 --- a/test-packages/ts-template-imports-app/types/index.d.ts +++ b/test-packages/ts-template-imports-app/types/index.d.ts @@ -28,10 +28,10 @@ declare global { MyCustomElement: MyCustomElement; } - interface GlintHtmlElementAttributesMap { - 'my-custom-element': { - propNum: number; - propStr: string; - }; - } + // interface GlintHtmlElementAttributesMap { + // 'my-custom-element': { + // propNum: number; + // propStr: string; + // }; + // } } From 98617754eccd21c2d582c565f5fa46cd49abbca5 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:41:14 -0500 Subject: [PATCH 15/27] Revert changes to types.d.ts for now --- packages/template/-private/dsl/types.d.ts | 33 +++++++---------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 15afd876..d2efe711 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -2,6 +2,8 @@ import './elements'; import { AttrValue } from '../index'; import { GlintElementRegistry } from './lib.dom.augmentation'; +type Registry = GlintElementRegistry; + /** * This doesn't generate _totally_ unique mappings, but they all have the same attributes. * @@ -14,24 +16,13 @@ import { GlintElementRegistry } from './lib.dom.augmentation'; * * And for the purposes of attribute lookup, that's good enough. */ -export type Lookup = { - [K in keyof GlintElementRegistry]: [GlintElementRegistry[K]] extends [T] // check assignability in one direction - ? [T] extends [GlintElementRegistry[K]] // and in the other - ? K // if both true, exact match - : never - : never; -}[keyof GlintElementRegistry]; - -/** - * Same thing, but for CustomElements - */ -export type CustomElementLookup = { - [K in keyof GlintCustomElementRegistry]: [GlintCustomElementRegistry[K]] extends [T] // check assignability in one direction - ? [T] extends [GlintCustomElementRegistry[K]] // and in the other +type Lookup = { + [K in keyof Registry]: [Registry[K]] extends [T] // check assignability in one direction + ? [T] extends [Registry[K]] // and in the other ? K // if both true, exact match : never : never; -}[keyof GlintCustomElementRegistry]; +}[keyof Registry]; /** * A utility for constructing the type of an environment's `resolveOrReturn` from @@ -45,9 +36,7 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - // : Name extends keyof GlintCustomElementRegistry - // ? GlintCustomElementRegistry[Name] - : Element; + : Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Name] @@ -65,8 +54,6 @@ export type AttributesForElement> = : // Or is K in the SVG attributes map? K extends keyof GlintSvgElementAttributesMap ? WithDataAttributes - // : K extends keyof GlintCustomElementRegistry - // ? WithDataAttributes - : // If the element can't be found: fallback to just allow general AttrValue - // NOTE: MathML has no attributes - Record; + : // If the element can't be found: fallback to just allow general AttrValue + // NOTE: MathML has no attributes + Record; From 64a7148e1c1ac30903746437488e5a2848e53130 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:06:23 -0500 Subject: [PATCH 16/27] New baseline --- packages/template/-private/dsl/elements.d.ts | 2 +- .../-private/dsl/lib.dom.augmentation.d.ts | 4 +--- packages/template/-private/dsl/types.d.ts | 16 +++++++++------- packages/template/__tests__/attributes.test.ts | 13 ++++--------- packages/template/__tests__/augmentation.test.ts | 13 +++---------- .../template/__tests__/custom-element.test.ts | 4 ++-- .../private/AttributesForElement.test.ts | 6 ++++++ .../__tests__/private/ElementForTagName.test.ts | 10 +++++++--- 8 files changed, 33 insertions(+), 35 deletions(-) diff --git a/packages/template/-private/dsl/elements.d.ts b/packages/template/-private/dsl/elements.d.ts index 302c9a80..39d21ee3 100644 --- a/packages/template/-private/dsl/elements.d.ts +++ b/packages/template/-private/dsl/elements.d.ts @@ -4,7 +4,7 @@ import { AttrValue } from '../index'; declare global { - interface GlintCustomElementRegistry { + interface GlintCustomElementMap { // e.g.: // 'my-button': MyButtonElement; } diff --git a/packages/template/-private/dsl/lib.dom.augmentation.d.ts b/packages/template/-private/dsl/lib.dom.augmentation.d.ts index bd0201bd..b4df69f6 100644 --- a/packages/template/-private/dsl/lib.dom.augmentation.d.ts +++ b/packages/template/-private/dsl/lib.dom.augmentation.d.ts @@ -7,8 +7,7 @@ // but it does not provide unique types for each element, and the technique // we use for looking up the type-string for each element does not work with the built in types. -declare global { -interface GlintElementRegistry { +export interface GlintElementRegistry { 'SVGElement': SVGElement; 'HTMLAnchorElement': HTMLAnchorElement; 'HTMLElement': HTMLElement; @@ -137,4 +136,3 @@ interface GlintElementRegistry { 'SVGUseElement': SVGUseElement; 'SVGViewElement': SVGViewElement; } -} \ No newline at end of file diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index d2efe711..a41fe9a5 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -2,8 +2,6 @@ import './elements'; import { AttrValue } from '../index'; import { GlintElementRegistry } from './lib.dom.augmentation'; -type Registry = GlintElementRegistry; - /** * This doesn't generate _totally_ unique mappings, but they all have the same attributes. * @@ -16,13 +14,13 @@ type Registry = GlintElementRegistry; * * And for the purposes of attribute lookup, that's good enough. */ -type Lookup = { - [K in keyof Registry]: [Registry[K]] extends [T] // check assignability in one direction - ? [T] extends [Registry[K]] // and in the other +export type Lookup = { + [K in keyof GlintElementRegistry]: [GlintElementRegistry[K]] extends [T] // check assignability in one direction + ? [T] extends [GlintElementRegistry[K]] // and in the other ? K // if both true, exact match : never : never; -}[keyof Registry]; +}[keyof GlintElementRegistry]; /** * A utility for constructing the type of an environment's `resolveOrReturn` from @@ -36,7 +34,11 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - : Element; + // By default, the GlintCustomElementMap is empty + : Name extends keyof GlintCustomElementMap + ? GlintCustomElementMap[Name] + // If there is no match, we can fallback to the originating ancestor Element type + : Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Name] diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index 31c94bcf..afb7addd 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -62,20 +62,15 @@ class MyComponent extends TestComponent<{ Element: HTMLImageElement }> { expectTypeOf(el).toEqualTypeOf<{ element: Element }>(); } -class RegisteredCustomElement extends HTMLElement {} -interface RegisteredCustomElementAttributes { - propNum: number; - propStr: string; +class RegisteredCustomElement extends HTMLElement { + declare propNum: number; + declare propStr: string; } declare global { - interface GlintCustomElementRegistry { + interface GlintCustomElementMap { 'registered-custom-element': RegisteredCustomElement; } - - // interface GlintHtmlElementAttributesMap { - // 'registered-custom-element': RegisteredCustomElementAttributes; - // } } { diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index 374ca09d..81efda06 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -5,21 +5,14 @@ import '@glint/template'; class AugmentedCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; + declare propNum: number; + declare propStr: string; declare static readonly __brand: unique symbol; } declare global { - interface GlintCustomElementRegistry { + interface GlintCustomElementMap { AugmentedCustomElement: AugmentedCustomElement; 'augmented-custom-element': AugmentedCustomElement; } - - // interface GlintHtmlElementAttributesMap { - // AugmentedCustomElement: { - // propNum: number; - // propStr: string; - // }; - // } } diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index d45e49a1..448b8a66 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -26,8 +26,8 @@ import { AttrValue } from '../-private'; * (yes) */ { - expectTypeOf().toHaveProperty('AugmentedCustomElement'); - expectTypeOf().toHaveProperty('augmented-custom-element'); + expectTypeOf().toHaveProperty('AugmentedCustomElement'); + expectTypeOf().toHaveProperty('augmented-custom-element'); expectTypeOf().toHaveProperty('AugmentedCustomElement'); const custom = emitElement('augmented-custom-element'); diff --git a/packages/template/__tests__/private/AttributesForElement.test.ts b/packages/template/__tests__/private/AttributesForElement.test.ts index d0057b2d..8de6722e 100644 --- a/packages/template/__tests__/private/AttributesForElement.test.ts +++ b/packages/template/__tests__/private/AttributesForElement.test.ts @@ -31,6 +31,7 @@ import type { AttributesForElement } from '../../-private/dsl'; expectTypeOf<'version' | 'fill'>().toExtend(); } + { type Attributes = keyof AttributesForElement; expectTypeOf().toEqualTypeOf<`data-${string}` | keyof HTMLImageElementAttributes>(); @@ -39,6 +40,11 @@ import type { AttributesForElement } from '../../-private/dsl'; expectTypeOf().toBeString(); expectTypeOf<'alt' | 'src'>().toExtend(); } +{ + type AttributeMap = AttributesForElement; + expectTypeOf().not.toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); +} { type Attributes = keyof AttributesForElement & string; diff --git a/packages/template/__tests__/private/ElementForTagName.test.ts b/packages/template/__tests__/private/ElementForTagName.test.ts index f82152b6..e8af2b6c 100644 --- a/packages/template/__tests__/private/ElementForTagName.test.ts +++ b/packages/template/__tests__/private/ElementForTagName.test.ts @@ -4,12 +4,15 @@ import { expectTypeOf } from 'expect-type'; import type { ElementForTagName } from '../../-private/dsl/types'; class MyCustomElement extends HTMLElement { - propNum!: number; - propStr!: string; + declare propNum: number; + declare propStr: string; + declare propBool: boolean; + + declare static readonly __brand: unique symbol; } declare global { - interface GlintCustomElementRegistry { + interface GlintCustomElementMap { 'my-custom-element-element-for-tag-name': MyCustomElement; } } @@ -20,6 +23,7 @@ declare global { expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); expectTypeOf().not.toEqualTypeOf(); } From 889dd168e32a3489742275a4191e91ed96264554 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:18:42 -0500 Subject: [PATCH 17/27] This is hard --- bin/build-elements.mjs | 37 ++++- .../-private/dsl/custom-elements.d.ts | 28 ++-- packages/template/-private/dsl/elements.d.ts | 132 +++++++++++++++++- packages/template/-private/dsl/emit.d.ts | 7 +- packages/template/-private/dsl/types.d.ts | 41 +++++- .../template/__tests__/augmentation.test.ts | 11 +- .../template/__tests__/custom-element.test.ts | 31 ++-- 7 files changed, 243 insertions(+), 44 deletions(-) diff --git a/bin/build-elements.mjs b/bin/build-elements.mjs index 4a113662..08dc741b 100644 --- a/bin/build-elements.mjs +++ b/bin/build-elements.mjs @@ -48,6 +48,17 @@ const htmlElementsMap = new Map([[GLOBAL_HTML_ATTRIBUTES_NAME, 'HTMLElement']]); const svgElementsMap = new Map([[GLOBAL_SVG_ATTRIBUTES_NAME, 'SVGElement']]); const mathmlElementsMap = new Map(); +const tagNameAttributesMap = []; + +function addTagNameAttributesMapEntry(name, interfaceName) { + if (name === 'GlobalHTMLAttributes' || name === 'GlobalSVGAttributes') { + + return; + } + + tagNameAttributesMap.push(` ['${name}']: ${interfaceName};`); +} + traverse(ast, { TSInterfaceDeclaration: function (path) { if (path.node.id.name === 'HTMLElementTagNameMap') { @@ -85,10 +96,6 @@ function createHtmlElementsAttributesMap() { import { AttrValue } from '../index'; declare global { - interface GlintCustomElementRegistry { - // e.g.: - // 'my-button': MyButtonElement; - } `; const processed = new Set(); @@ -127,6 +134,7 @@ interface GlintHtmlElementAttributesMap {\n`; }); } htmlElementsContent += '}\n'; + addTagNameAttributesMapEntry(name, interfaceName); } function addMapEntry(type) { @@ -197,6 +205,7 @@ interface GlintSvgElementAttributesMap {\n`; }); } svgElementsContent += `}\n`; + addTagNameAttributesMapEntry(name, interfaceName); } function addMapEntry(type) { @@ -236,5 +245,23 @@ const filePath = resolve( fileURLToPath(import.meta.url), '../../packages/template/-private/dsl/elements.d.ts', ); -const content = prefix + createHtmlElementsAttributesMap() + createSvgElementAttributesMap(); + +function defineTagNameAttributesMap(contents) { + return [ + '', + `global {`, + `/* These are not all the elements, but they are the ones with types of their own, beyond HTMLElement/SVGElement */`, + `interface GlintTagNameAttributesMap {`, + contents.join('\n'), + `}`, + `}`, + `\n`, + ].join('\n'); +} + +const content = + prefix + + createHtmlElementsAttributesMap() + + createSvgElementAttributesMap() + + defineTagNameAttributesMap(tagNameAttributesMap); writeFileSync(filePath, content); diff --git a/packages/template/-private/dsl/custom-elements.d.ts b/packages/template/-private/dsl/custom-elements.d.ts index 5572a4bb..31a50d9e 100644 --- a/packages/template/-private/dsl/custom-elements.d.ts +++ b/packages/template/-private/dsl/custom-elements.d.ts @@ -12,22 +12,34 @@ declare global { * } * ``` * - * When doing this, you'll also want your props and attributes to be typed, and that is configured - * through a separate declaration merge as TypeScript doesn't have a way of accessing which attributes/props - * are valid for a given element type. - * + */ + interface GlintCustomElementMap { + /* intentionally empty, as there are no custom elements by default */ + } + /** + * Map of custom element class names to their attributes type. + * + * This is a separate interface, because there isn't a TypeScript mechanism + * to get the list of attributes and properties assignable to a given element type. + * + * You _could_ set loose values such as `typeof YourELement`, but then you'll have things + * that don't make sense to assign in the template, such as methods (toString, etc) + * * ```ts * declare global { - * interface GlintHtmlElementAttributesMap { - * 'my-custom-element': { + * interface GlintCustomElementAttributesMap { + * // ok, with caveats, easiest. + * 'MyCustomElementClass': typeof MyCustomElementClass; + * // better, but more verbose + * 'MyCustomElementClass': { * propNum: number; * propStr: string; - * }; + * // etc * } * } * ``` */ - interface GlintCustomElementRegistry { + interface GlintCustomElementAttributesMap { /* intentionally empty, as there are no custom elements by default */ } } diff --git a/packages/template/-private/dsl/elements.d.ts b/packages/template/-private/dsl/elements.d.ts index 39d21ee3..92931c3e 100644 --- a/packages/template/-private/dsl/elements.d.ts +++ b/packages/template/-private/dsl/elements.d.ts @@ -4,10 +4,6 @@ import { AttrValue } from '../index'; declare global { - interface GlintCustomElementMap { - // e.g.: - // 'my-button': MyButtonElement; - } interface GlobalAriaAttributes { ['aria-activedescendant']: AttrValue; ['aria-atomic']: AttrValue; @@ -4400,3 +4396,131 @@ interface GlintSvgElementAttributesMap { ['SVGElement']: GlobalSVGAttributes; } } + +global { +/* These are not all the elements, but they are the ones with types of their own, beyond HTMLElement/SVGElement */ +interface GlintTagNameAttributesMap { + ['a']: HTMLAnchorElementAttributes; + ['area']: HTMLAreaElementAttributes; + ['audio']: HTMLAudioElementAttributes; + ['base']: HTMLBaseElementAttributes; + ['blockquote']: HTMLQuoteElementAttributes; + ['br']: HTMLBRElementAttributes; + ['button']: HTMLButtonElementAttributes; + ['canvas']: HTMLCanvasElementAttributes; + ['caption']: HTMLTableCaptionElementAttributes; + ['col']: HTMLTableColElementAttributes; + ['data']: HTMLDataElementAttributes; + ['del']: HTMLModElementAttributes; + ['details']: HTMLDetailsElementAttributes; + ['dialog']: HTMLDialogElementAttributes; + ['div']: HTMLDivElementAttributes; + ['dl']: HTMLDListElementAttributes; + ['embed']: HTMLEmbedElementAttributes; + ['fieldset']: HTMLFieldSetElementAttributes; + ['form']: HTMLFormElementAttributes; + ['h1']: HTMLHeadingElementAttributes; + ['head']: HTMLHeadElementAttributes; + ['hr']: HTMLHRElementAttributes; + ['iframe']: HTMLIFrameElementAttributes; + ['img']: HTMLImageElementAttributes; + ['input']: HTMLInputElementAttributes; + ['label']: HTMLLabelElementAttributes; + ['legend']: HTMLLegendElementAttributes; + ['li']: HTMLLIElementAttributes; + ['link']: HTMLLinkElementAttributes; + ['map']: HTMLMapElementAttributes; + ['menu']: HTMLMenuElementAttributes; + ['meta']: HTMLMetaElementAttributes; + ['meter']: HTMLMeterElementAttributes; + ['object']: HTMLObjectElementAttributes; + ['ol']: HTMLOListElementAttributes; + ['optgroup']: HTMLOptGroupElementAttributes; + ['option']: HTMLOptionElementAttributes; + ['output']: HTMLOutputElementAttributes; + ['p']: HTMLParagraphElementAttributes; + ['pre']: HTMLPreElementAttributes; + ['progress']: HTMLProgressElementAttributes; + ['script']: HTMLScriptElementAttributes; + ['select']: HTMLSelectElementAttributes; + ['slot']: HTMLSlotElementAttributes; + ['source']: HTMLSourceElementAttributes; + ['style']: HTMLStyleElementAttributes; + ['table']: HTMLTableElementAttributes; + ['tbody']: HTMLTableSectionElementAttributes; + ['td']: HTMLTableCellElementAttributes; + ['template']: HTMLTemplateElementAttributes; + ['textarea']: HTMLTextAreaElementAttributes; + ['time']: HTMLTimeElementAttributes; + ['tr']: HTMLTableRowElementAttributes; + ['track']: HTMLTrackElementAttributes; + ['ul']: HTMLUListElementAttributes; + ['video']: HTMLVideoElementAttributes; + ['HTMLElement']: HTMLElementAttributes; + ['a']: SVGAElementAttributes; + ['animate']: SVGAnimateElementAttributes; + ['animateMotion']: SVGAnimateMotionElementAttributes; + ['animateTransform']: SVGAnimateTransformElementAttributes; + ['circle']: SVGCircleElementAttributes; + ['clipPath']: SVGClipPathElementAttributes; + ['defs']: SVGDefsElementAttributes; + ['desc']: SVGDescElementAttributes; + ['ellipse']: SVGEllipseElementAttributes; + ['feBlend']: SVGFEBlendElementAttributes; + ['feColorMatrix']: SVGFEColorMatrixElementAttributes; + ['feComponentTransfer']: SVGFEComponentTransferElementAttributes; + ['feComposite']: SVGFECompositeElementAttributes; + ['feConvolveMatrix']: SVGFEConvolveMatrixElementAttributes; + ['feDiffuseLighting']: SVGFEDiffuseLightingElementAttributes; + ['feDisplacementMap']: SVGFEDisplacementMapElementAttributes; + ['feDistantLight']: SVGFEDistantLightElementAttributes; + ['feDropShadow']: SVGFEDropShadowElementAttributes; + ['feFlood']: SVGFEFloodElementAttributes; + ['feFuncA']: SVGFEFuncAElementAttributes; + ['feFuncB']: SVGFEFuncBElementAttributes; + ['feFuncG']: SVGFEFuncGElementAttributes; + ['feFuncR']: SVGFEFuncRElementAttributes; + ['feGaussianBlur']: SVGFEGaussianBlurElementAttributes; + ['feImage']: SVGFEImageElementAttributes; + ['feMerge']: SVGFEMergeElementAttributes; + ['feMergeNode']: SVGFEMergeNodeElementAttributes; + ['feMorphology']: SVGFEMorphologyElementAttributes; + ['feOffset']: SVGFEOffsetElementAttributes; + ['fePointLight']: SVGFEPointLightElementAttributes; + ['feSpecularLighting']: SVGFESpecularLightingElementAttributes; + ['feSpotLight']: SVGFESpotLightElementAttributes; + ['feTile']: SVGFETileElementAttributes; + ['feTurbulence']: SVGFETurbulenceElementAttributes; + ['filter']: SVGFilterElementAttributes; + ['foreignObject']: SVGForeignObjectElementAttributes; + ['g']: SVGGElementAttributes; + ['image']: SVGImageElementAttributes; + ['line']: SVGLineElementAttributes; + ['linearGradient']: SVGLinearGradientElementAttributes; + ['marker']: SVGMarkerElementAttributes; + ['mask']: SVGMaskElementAttributes; + ['metadata']: SVGMetadataElementAttributes; + ['mpath']: SVGMPathElementAttributes; + ['path']: SVGPathElementAttributes; + ['pattern']: SVGPatternElementAttributes; + ['polygon']: SVGPolygonElementAttributes; + ['polyline']: SVGPolylineElementAttributes; + ['radialGradient']: SVGRadialGradientElementAttributes; + ['rect']: SVGRectElementAttributes; + ['script']: SVGScriptElementAttributes; + ['set']: SVGSetElementAttributes; + ['stop']: SVGStopElementAttributes; + ['style']: SVGStyleElementAttributes; + ['svg']: SVGSVGElementAttributes; + ['switch']: SVGSwitchElementAttributes; + ['symbol']: SVGSymbolElementAttributes; + ['text']: SVGTextElementAttributes; + ['textPath']: SVGTextPathElementAttributes; + ['title']: SVGTitleElementAttributes; + ['tspan']: SVGTSpanElementAttributes; + ['use']: SVGUseElementAttributes; + ['view']: SVGViewElementAttributes; + ['SVGElement']: SVGElementAttributes; +} +} + diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index acdede02..263a806b 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -57,6 +57,7 @@ export declare function emitElement( : Name extends 'svg' ? SVGElementForTagName<'svg'> : ElementForTagName; + attributes: AttributesForTagName; }; export declare function emitSVGElement( @@ -154,9 +155,9 @@ export declare function applySplattributes< *
* */ -export declare function applyAttributes( - element: T, - attrs: Partial>, +export declare function applyAttributes( + element: T['element'], + attrs: Partial, ): void; /* diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index a41fe9a5..a33dc294 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -1,4 +1,5 @@ import './elements'; +import './custom-elements'; import { AttrValue } from '../index'; import { GlintElementRegistry } from './lib.dom.augmentation'; @@ -22,6 +23,14 @@ export type Lookup = { : never; }[keyof GlintElementRegistry]; +export type CustomElementLookup = { + [K in keyof GlintCustomElementMap]: [GlintCustomElementMap[K]] extends [T] + ? [T] extends [GlintCustomElementMap[K]] + ? K + : never + : never; +}[keyof GlintCustomElementMap]; + /** * A utility for constructing the type of an environment's `resolveOrReturn` from * the type of its `resolve` function. @@ -34,11 +43,11 @@ export type ResolveOrReturn = T & ((item: U) => () => U); */ export type ElementForTagName = Name extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[Name] - // By default, the GlintCustomElementMap is empty - : Name extends keyof GlintCustomElementMap + : // By default, the GlintCustomElementMap is empty + Name extends keyof GlintCustomElementMap ? GlintCustomElementMap[Name] - // If there is no match, we can fallback to the originating ancestor Element type - : Element; + : // If there is no match, we can fallback to the originating ancestor Element type + Element; export type SVGElementForTagName = Name extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[Name] @@ -49,7 +58,20 @@ export type MathMlElementForTagName = type WithDataAttributes = T & Record<`data-${string}`, AttrValue>; -export type AttributesForElement> = +export type AttributesForKeyInMap = K extends keyof M + ? WithDataAttributes + : K extends never + ? `Invalid key passed (never)` + : `key "${K}" not found in map`; + +export type AttributesForCustomElement< + Elem extends Element, + K = CustomElementLookup, +> = keyof Elem & K extends keyof GlintCustomElementAttributesMap + ? AttributesForKeyInMap + : 'Could not find custom element'; + +export type AttributesForStandardElement> = // Is K in the HTML attributes map? K extends keyof GlintHtmlElementAttributesMap ? WithDataAttributes @@ -59,3 +81,12 @@ export type AttributesForElement> = : // If the element can't be found: fallback to just allow general AttrValue // NOTE: MathML has no attributes Record; + +export type AttributesForElement< + Elem extends Element, + K = Lookup, +> = AttributesForStandardElement; // | AttributesForCustomElement; + +export type AttributesForTagName = Name extends keyof GlintTagNameAttributesMap + ? GlintTagNameAttributesMap[Name] + : GlintTagNameAttributesMap['HTMLElement']; diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index 81efda06..d8159ce5 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -3,16 +3,19 @@ */ import '@glint/template'; +const BRAND = Symbol('AugmentedCustomElement brand'); -class AugmentedCustomElement extends HTMLElement { +export class AugmentedCustomElement extends HTMLElement { declare propNum: number; declare propStr: string; - declare static readonly __brand: unique symbol; + declare static readonly [BRAND]: unique symbol; } declare global { interface GlintCustomElementMap { - AugmentedCustomElement: AugmentedCustomElement; - 'augmented-custom-element': AugmentedCustomElement; + 'augmented-custom-element': typeof AugmentedCustomElement; + } + interface GlintTagNameAttributesMap { + 'augmented-custom-element': typeof AugmentedCustomElement; } } diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 448b8a66..79e90e1a 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -2,18 +2,21 @@ import { expectTypeOf } from 'expect-type'; import { emitElement, applyAttributes, - AttributesForElement, CustomElementLookup, + WithDataAttributes, + AttributesForTagName, } from '../-private/dsl'; import { AttrValue } from '../-private'; +import { AugmentedCustomElement } from './augmentation.test'; /** * Baseline */ { const div = emitElement('div'); - expectTypeOf(div).toEqualTypeOf<{ element: HTMLDivElement }>(); - expectTypeOf().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf(div.element).toEqualTypeOf(); + expectTypeOf(div.attributes).toEqualTypeOf>(); applyAttributes(div.element, { 'data-foo': 123, @@ -26,28 +29,26 @@ import { AttrValue } from '../-private'; * (yes) */ { - expectTypeOf().toHaveProperty('AugmentedCustomElement'); + // expectTypeOf().toHaveProperty('AugmentedCustomElement'); expectTypeOf().toHaveProperty('augmented-custom-element'); - expectTypeOf().toHaveProperty('AugmentedCustomElement'); const custom = emitElement('augmented-custom-element'); - type ElementName = CustomElementLookup; - expectTypeOf().not.toBeNever(); - expectTypeOf().toEqualTypeOf<'AugmentedCustomElement' | 'augmented-custom-element' | 'my-custom-element-element-for-tag-name'>(); + type L = CustomElementLookup; + expectTypeOf().toEqualTypeOf<'augmented-custom-element'>(); - type FoundAttrs = AttributesForElement; - expectTypeOf().not.toBeNever(); - expectTypeOf().toHaveProperty('propNum'); - // The default type for attributes - expectTypeOf().not.toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf(custom.element).toEqualTypeOf(); + expectTypeOf(custom.attributes).toEqualTypeOf>(); + type X = keyof typeof custom.attributes; + expectTypeOf().toEqualTypeOf<`propNum` | `propStr`>(); - applyAttributes(custom.element, { + applyAttributes(custom, { propNum: 123, propStr: 'hello', }); - applyAttributes(custom.element, { + applyAttributes(custom, { // @ts-expect-error propNum expects a number, and I gave it a string to test that an error occurs propNum: 'wrong', // @ts-expect-error propStr expects a string, and I gave it a number to test that an error occurs From 49052964e2ded15a05dd308a2c4e07c4f0aa49fb Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:46:46 -0500 Subject: [PATCH 18/27] Hm --- packages/template/-private/dsl/emit.d.ts | 4 ++-- packages/template/-private/dsl/types.d.ts | 4 ++++ packages/template/__tests__/augmentation.test.ts | 7 ++++++- packages/template/__tests__/custom-element.test.ts | 11 ++++++----- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index 263a806b..a657ebda 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -10,7 +10,7 @@ import { NamedArgs, } from '../integration'; import { - AttributesForElement, + AttributeRecord, ElementForTagName, MathMlElementForTagName, SVGElementForTagName, @@ -155,7 +155,7 @@ export declare function applySplattributes< *
* */ -export declare function applyAttributes( +export declare function applyAttributes( element: T['element'], attrs: Partial, ): void; diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index a33dc294..38d2c631 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -90,3 +90,7 @@ export type AttributesForElement< export type AttributesForTagName = Name extends keyof GlintTagNameAttributesMap ? GlintTagNameAttributesMap[Name] : GlintTagNameAttributesMap['HTMLElement']; + + export type AttributeRecord = { + [K in (keyof RecordType)]: RecordType[K]; + } \ No newline at end of file diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index d8159ce5..c85fa2b7 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -11,11 +11,16 @@ export class AugmentedCustomElement extends HTMLElement { declare static readonly [BRAND]: unique symbol; } +export interface AugmentedCustomElementAttributes { + propNum: number; + propStr: string; +} + declare global { interface GlintCustomElementMap { 'augmented-custom-element': typeof AugmentedCustomElement; } interface GlintTagNameAttributesMap { - 'augmented-custom-element': typeof AugmentedCustomElement; + 'augmented-custom-element': AugmentedCustomElementAttributes; } } diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 79e90e1a..408d55b0 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -7,7 +7,7 @@ import { AttributesForTagName, } from '../-private/dsl'; import { AttrValue } from '../-private'; -import { AugmentedCustomElement } from './augmentation.test'; +import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augmentation.test'; /** * Baseline @@ -37,11 +37,12 @@ import { AugmentedCustomElement } from './augmentation.test'; type L = CustomElementLookup; expectTypeOf().toEqualTypeOf<'augmented-custom-element'>(); - expectTypeOf>().toEqualTypeOf(); + type Attrs = AttributesForTagName<`augmented-custom-element`>; + expectTypeOf().toEqualTypeOf(); + + expectTypeOf>().toEqualTypeOf(); expectTypeOf(custom.element).toEqualTypeOf(); - expectTypeOf(custom.attributes).toEqualTypeOf>(); - type X = keyof typeof custom.attributes; - expectTypeOf().toEqualTypeOf<`propNum` | `propStr`>(); + expectTypeOf(custom.attributes).toEqualTypeOf>(); applyAttributes(custom, { propNum: 123, From 0ca0fbbfaf3c295fc4a7e8befee891d8007f4f1d Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:53:24 -0500 Subject: [PATCH 19/27] hm --- packages/template/-private/dsl/emit.d.ts | 8 ++++---- packages/template/__tests__/custom-element.test.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index a657ebda..ef880315 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -10,7 +10,6 @@ import { NamedArgs, } from '../integration'; import { - AttributeRecord, ElementForTagName, MathMlElementForTagName, SVGElementForTagName, @@ -52,6 +51,7 @@ export declare function emitContent(value: ContentValue): void; export declare function emitElement( name: Name, ): { + name: Name; element: Name extends 'math' ? MathMlElementForTagName<'math'> : Name extends 'svg' @@ -155,9 +155,9 @@ export declare function applySplattributes< *
* */ -export declare function applyAttributes( - element: T['element'], - attrs: Partial, +export declare function applyAttributes( + element: Elem, + attrs: Partial>, ): void; /* diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 408d55b0..23ec8672 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -47,6 +47,8 @@ import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augm applyAttributes(custom, { propNum: 123, propStr: 'hello', + // @ts-expect-error propNope does not exist + propNope: 'wrong', }); applyAttributes(custom, { From f091c1b120d0b7740c08d599038a84a2b1cb3d74 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:50:46 -0500 Subject: [PATCH 20/27] It's working? --- packages/template/-private/dsl/emit.d.ts | 11 ++++++----- packages/template/-private/dsl/types.d.ts | 4 ++-- .../template/__tests__/augmentation.test.ts | 2 +- .../template/__tests__/custom-element.test.ts | 18 ++++++------------ 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index ef880315..5fcc0c29 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -10,6 +10,7 @@ import { NamedArgs, } from '../integration'; import { + AttributesForTagName, ElementForTagName, MathMlElementForTagName, SVGElementForTagName, @@ -62,11 +63,11 @@ export declare function emitElement( export declare function emitSVGElement( name: Name, -): { element: SVGElementForTagName }; +): { name: Name; element: SVGElementForTagName }; export declare function emitMathMlElement( name: Name, -): { element: MathMlElementForTagName }; +): { name: Name; element: MathMlElementForTagName }; /* * Emits the given value as an entity that expects to receive blocks @@ -155,9 +156,9 @@ export declare function applySplattributes< *
* */ -export declare function applyAttributes( - element: Elem, - attrs: Partial>, +export declare function applyAttributes( + invoked: T, + attrs: T['attributes'], ): void; /* diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 38d2c631..030e7e84 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -88,8 +88,8 @@ export type AttributesForElement< > = AttributesForStandardElement; // | AttributesForCustomElement; export type AttributesForTagName = Name extends keyof GlintTagNameAttributesMap - ? GlintTagNameAttributesMap[Name] - : GlintTagNameAttributesMap['HTMLElement']; + ? WithDataAttributes + : WithDataAttributes; export type AttributeRecord = { [K in (keyof RecordType)]: RecordType[K]; diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index c85fa2b7..97fb5180 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -8,7 +8,7 @@ const BRAND = Symbol('AugmentedCustomElement brand'); export class AugmentedCustomElement extends HTMLElement { declare propNum: number; declare propStr: string; - declare static readonly [BRAND]: unique symbol; + //declare static readonly [BRAND]: unique symbol; } export interface AugmentedCustomElementAttributes { diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 23ec8672..268f67fa 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -2,11 +2,9 @@ import { expectTypeOf } from 'expect-type'; import { emitElement, applyAttributes, - CustomElementLookup, WithDataAttributes, AttributesForTagName, } from '../-private/dsl'; -import { AttrValue } from '../-private'; import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augmentation.test'; /** @@ -14,12 +12,14 @@ import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augm */ { const div = emitElement('div'); - expectTypeOf>().toEqualTypeOf(); + + type Attrs = AttributesForTagName<`div`>; + expectTypeOf().toEqualTypeOf>(); expectTypeOf(div.element).toEqualTypeOf(); expectTypeOf(div.attributes).toEqualTypeOf>(); - applyAttributes(div.element, { - 'data-foo': 123, + applyAttributes(div, { + 'data-foo': "123", role: 'button', }); } @@ -29,18 +29,12 @@ import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augm * (yes) */ { - // expectTypeOf().toHaveProperty('AugmentedCustomElement'); expectTypeOf().toHaveProperty('augmented-custom-element'); const custom = emitElement('augmented-custom-element'); - type L = CustomElementLookup; - expectTypeOf().toEqualTypeOf<'augmented-custom-element'>(); - type Attrs = AttributesForTagName<`augmented-custom-element`>; - expectTypeOf().toEqualTypeOf(); - - expectTypeOf>().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf<'propNum' | 'propStr' | `data-${string}`>(); expectTypeOf(custom.element).toEqualTypeOf(); expectTypeOf(custom.attributes).toEqualTypeOf>(); From 650e359b0e4ae10a15fffb1598531dbb976eb7c3 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:06:49 -0500 Subject: [PATCH 21/27] Fix div --- packages/template/-private/dsl/emit.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index 5fcc0c29..5f189455 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -158,7 +158,7 @@ export declare function applySplattributes< */ export declare function applyAttributes( invoked: T, - attrs: T['attributes'], + attrs: Partial, ): void; /* From dada99467ac9c7ddbfeee3380e1ae17d5819c195 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:51:44 -0500 Subject: [PATCH 22/27] Progress? --- packages/template/-private/dsl/emit.d.ts | 11 ++++++-- packages/template/-private/dsl/types.d.ts | 9 ++++++- .../template/__tests__/attributes.test.ts | 25 ++++++++++++++++--- .../template/__tests__/emit-component.test.ts | 5 +++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index 5f189455..6c5058af 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -10,6 +10,7 @@ import { NamedArgs, } from '../integration'; import { + AttributesForElement, AttributesForTagName, ElementForTagName, MathMlElementForTagName, @@ -156,9 +157,15 @@ export declare function applySplattributes< *
* */ -export declare function applyAttributes( +export declare function applyAttributes< +T extends Element | { name: string; element: unknown, attributes: unknown }, +Attrs = T extends Element + ? Partial> + : Partial + > +( invoked: T, - attrs: Partial, + attrs: Attrs, ): void; /* diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 030e7e84..45d53831 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -93,4 +93,11 @@ export type AttributesForTagName = Name extends keyof Glint export type AttributeRecord = { [K in (keyof RecordType)]: RecordType[K]; - } \ No newline at end of file + } + + +export type ElementInfoForElementType = { + element: ElemType; + attributes: AttributesForElement; + name: 'unknown' +} \ No newline at end of file diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index afb7addd..d65786c9 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -5,10 +5,12 @@ import { applyAttributes, applyModifier, applySplattributes, + AttributesForTagName, emitComponent, emitElement, resolve, templateForBackingValue, + WithDataAttributes, } from '../-private/dsl'; import { ModifierLike } from '../-private/index'; import TestComponent from './test-component'; @@ -54,12 +56,16 @@ class MyComponent extends TestComponent<{ Element: HTMLImageElement }> { // `emitElement` type resolution { const el = emitElement('img'); - expectTypeOf(el).toEqualTypeOf<{ element: HTMLImageElement }>(); + expectTypeOf(el.element).toEqualTypeOf(); + expectTypeOf(el.name).toEqualTypeOf<'img'>(); + expectTypeOf(el.attributes).toEqualTypeOf>(); } { const el = emitElement('customelement'); - expectTypeOf(el).toEqualTypeOf<{ element: Element }>(); + expectTypeOf(el.element).toEqualTypeOf(); + expectTypeOf(el.name).toEqualTypeOf<'customelement'>(); + expectTypeOf(el.attributes).toEqualTypeOf>(); } class RegisteredCustomElement extends HTMLElement { @@ -70,12 +76,25 @@ class RegisteredCustomElement extends HTMLElement { declare global { interface GlintCustomElementMap { 'registered-custom-element': RegisteredCustomElement; + 'explicit-attributes': RegisteredCustomElement; + } + interface GlintTagNameAttributesMap { + 'explicit-attributes': { propNum: number; propStr: string }; } } { const el = emitElement('registered-custom-element'); - expectTypeOf(el).toEqualTypeOf<{ element: RegisteredCustomElement }>(); + expectTypeOf(el.element).toEqualTypeOf(); + expectTypeOf(el.name).toEqualTypeOf<'registered-custom-element'>(); + expectTypeOf(el.attributes).toEqualTypeOf>(); + + const el2 = emitElement('explicit-attributes'); + expectTypeOf(el2.element).toEqualTypeOf(); + expectTypeOf(el2.name).toEqualTypeOf<'explicit-attributes'>(); + expectTypeOf(el2.attributes).toEqualTypeOf< + WithDataAttributes<{ propNum: number; propStr: string }> + >(); } /** diff --git a/packages/template/__tests__/emit-component.test.ts b/packages/template/__tests__/emit-component.test.ts index 3c90e692..61ff45ff 100644 --- a/packages/template/__tests__/emit-component.test.ts +++ b/packages/template/__tests__/emit-component.test.ts @@ -9,6 +9,7 @@ import { templateForBackingValue, yieldToBlock, NamedArgsMarker, + WithDataAttributes, } from '../-private/dsl'; import TestComponent, { globals } from './test-component'; @@ -46,7 +47,9 @@ class MyComponent extends TestComponent> { { const __glintY__ = emitElement('div'); - expectTypeOf(__glintY__).toEqualTypeOf<{ element: HTMLDivElement }>(); + expectTypeOf(__glintY__.element).toEqualTypeOf(); + expectTypeOf(__glintY__.name).toEqualTypeOf<'div'>(); + expectTypeOf(__glintY__.attributes).toEqualTypeOf>(); applyModifier( resolve(globals.on)(__glintY__.element, 'click', __glintRef__.this.wrapperClicked), ); From b833a5ad246a76e21a35e514ae94c70d8ede6a0c Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:52:17 -0500 Subject: [PATCH 23/27] Progress? --- packages/template/__tests__/attributes.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/template/__tests__/attributes.test.ts b/packages/template/__tests__/attributes.test.ts index d65786c9..202b1f86 100644 --- a/packages/template/__tests__/attributes.test.ts +++ b/packages/template/__tests__/attributes.test.ts @@ -145,7 +145,9 @@ declare global { */ { const ctx = emitElement('a'); - expectTypeOf(ctx).toEqualTypeOf<{ element: HTMLAnchorElement }>(); + expectTypeOf(ctx.element).toEqualTypeOf(); + expectTypeOf(ctx.name).toEqualTypeOf<'a'>(); + expectTypeOf(ctx.attributes).toEqualTypeOf>(); applyModifier(resolve(anchorModifier)(ctx.element)); } From f00115af8b8b415e83f3dec15714a75866dcf0ad Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:39:36 -0500 Subject: [PATCH 24/27] yay --- packages/template/-private/dsl/emit.d.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index 6c5058af..efbb7c95 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -158,14 +158,12 @@ export declare function applySplattributes< * */ export declare function applyAttributes< -T extends Element | { name: string; element: unknown, attributes: unknown }, -Attrs = T extends Element - ? Partial> - : Partial - > -( + T extends Element | { name: string; element: unknown; attributes: unknown } +>( invoked: T, - attrs: Attrs, + attrs: T extends Element + ? { [K in string & keyof AttributesForElement]?: AttributesForElement[K] } + : { [K in string & keyof T['attributes']]?: T['attributes'][K] } ): void; /* From ef42fedd54840e97b7dfa58aefc24ae9c53568ab Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Sat, 8 Nov 2025 14:40:51 -0500 Subject: [PATCH 25/27] Lint:fix --- bin/build-elements.mjs | 1 - packages/template/-private/dsl/custom-elements.d.ts | 10 +++++----- packages/template/-private/dsl/emit.d.ts | 4 ++-- packages/template/-private/dsl/types.d.ts | 11 +++++------ packages/template/__tests__/custom-element.test.ts | 8 +++++--- packages/template/__tests__/emit-component.test.ts | 4 +++- .../__tests__/private/AttributesForElement.test.ts | 1 - 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/bin/build-elements.mjs b/bin/build-elements.mjs index 08dc741b..40312b3f 100644 --- a/bin/build-elements.mjs +++ b/bin/build-elements.mjs @@ -52,7 +52,6 @@ const tagNameAttributesMap = []; function addTagNameAttributesMapEntry(name, interfaceName) { if (name === 'GlobalHTMLAttributes' || name === 'GlobalSVGAttributes') { - return; } diff --git a/packages/template/-private/dsl/custom-elements.d.ts b/packages/template/-private/dsl/custom-elements.d.ts index 31a50d9e..ce0b6f70 100644 --- a/packages/template/-private/dsl/custom-elements.d.ts +++ b/packages/template/-private/dsl/custom-elements.d.ts @@ -18,13 +18,13 @@ declare global { } /** * Map of custom element class names to their attributes type. - * - * This is a separate interface, because there isn't a TypeScript mechanism + * + * This is a separate interface, because there isn't a TypeScript mechanism * to get the list of attributes and properties assignable to a given element type. - * - * You _could_ set loose values such as `typeof YourELement`, but then you'll have things + * + * You _could_ set loose values such as `typeof YourELement`, but then you'll have things * that don't make sense to assign in the template, such as methods (toString, etc) - * + * * ```ts * declare global { * interface GlintCustomElementAttributesMap { diff --git a/packages/template/-private/dsl/emit.d.ts b/packages/template/-private/dsl/emit.d.ts index efbb7c95..3baffe4c 100644 --- a/packages/template/-private/dsl/emit.d.ts +++ b/packages/template/-private/dsl/emit.d.ts @@ -158,12 +158,12 @@ export declare function applySplattributes< * */ export declare function applyAttributes< - T extends Element | { name: string; element: unknown; attributes: unknown } + T extends Element | { name: string; element: unknown; attributes: unknown }, >( invoked: T, attrs: T extends Element ? { [K in string & keyof AttributesForElement]?: AttributesForElement[K] } - : { [K in string & keyof T['attributes']]?: T['attributes'][K] } + : { [K in string & keyof T['attributes']]?: T['attributes'][K] }, ): void; /* diff --git a/packages/template/-private/dsl/types.d.ts b/packages/template/-private/dsl/types.d.ts index 45d53831..78e44c59 100644 --- a/packages/template/-private/dsl/types.d.ts +++ b/packages/template/-private/dsl/types.d.ts @@ -91,13 +91,12 @@ export type AttributesForTagName = Name extends keyof Glint ? WithDataAttributes : WithDataAttributes; - export type AttributeRecord = { - [K in (keyof RecordType)]: RecordType[K]; - } - +export type AttributeRecord = { + [K in keyof RecordType]: RecordType[K]; +}; export type ElementInfoForElementType = { element: ElemType; attributes: AttributesForElement; - name: 'unknown' -} \ No newline at end of file + name: 'unknown'; +}; diff --git a/packages/template/__tests__/custom-element.test.ts b/packages/template/__tests__/custom-element.test.ts index 268f67fa..8e705e8c 100644 --- a/packages/template/__tests__/custom-element.test.ts +++ b/packages/template/__tests__/custom-element.test.ts @@ -19,7 +19,7 @@ import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augm expectTypeOf(div.attributes).toEqualTypeOf>(); applyAttributes(div, { - 'data-foo': "123", + 'data-foo': '123', role: 'button', }); } @@ -36,13 +36,15 @@ import { AugmentedCustomElement, AugmentedCustomElementAttributes } from './augm type Attrs = AttributesForTagName<`augmented-custom-element`>; expectTypeOf().toEqualTypeOf<'propNum' | 'propStr' | `data-${string}`>(); expectTypeOf(custom.element).toEqualTypeOf(); - expectTypeOf(custom.attributes).toEqualTypeOf>(); + expectTypeOf(custom.attributes).toEqualTypeOf< + WithDataAttributes + >(); applyAttributes(custom, { propNum: 123, propStr: 'hello', // @ts-expect-error propNope does not exist - propNope: 'wrong', + propNope: 'wrong', }); applyAttributes(custom, { diff --git a/packages/template/__tests__/emit-component.test.ts b/packages/template/__tests__/emit-component.test.ts index 61ff45ff..07df46a4 100644 --- a/packages/template/__tests__/emit-component.test.ts +++ b/packages/template/__tests__/emit-component.test.ts @@ -49,7 +49,9 @@ class MyComponent extends TestComponent> { const __glintY__ = emitElement('div'); expectTypeOf(__glintY__.element).toEqualTypeOf(); expectTypeOf(__glintY__.name).toEqualTypeOf<'div'>(); - expectTypeOf(__glintY__.attributes).toEqualTypeOf>(); + expectTypeOf(__glintY__.attributes).toEqualTypeOf< + WithDataAttributes + >(); applyModifier( resolve(globals.on)(__glintY__.element, 'click', __glintRef__.this.wrapperClicked), ); diff --git a/packages/template/__tests__/private/AttributesForElement.test.ts b/packages/template/__tests__/private/AttributesForElement.test.ts index 8de6722e..f59ab8a7 100644 --- a/packages/template/__tests__/private/AttributesForElement.test.ts +++ b/packages/template/__tests__/private/AttributesForElement.test.ts @@ -31,7 +31,6 @@ import type { AttributesForElement } from '../../-private/dsl'; expectTypeOf<'version' | 'fill'>().toExtend(); } - { type Attributes = keyof AttributesForElement; expectTypeOf().toEqualTypeOf<`data-${string}` | keyof HTMLImageElementAttributes>(); From f40886b852b2c57ca0246eb204fb4aa99afd2af2 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Mon, 10 Nov 2025 14:38:54 -0500 Subject: [PATCH 26/27] No way --- .../template/template-to-typescript.ts | 4 +-- packages/template/-private/dsl/elements.d.ts | 3 -- .../template/__tests__/augmentation.test.ts | 3 -- .../transform/template-to-typescript.test.ts | 30 +++++++++++++++++++ .../src/custom-elements.gts | 2 +- .../ts-template-imports-app/types/index.d.ts | 18 +++++------ 6 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/core/src/transform/template/template-to-typescript.ts b/packages/core/src/transform/template/template-to-typescript.ts index 2481658d..94b6376a 100644 --- a/packages/core/src/transform/template/template-to-typescript.ts +++ b/packages/core/src/transform/template/template-to-typescript.ts @@ -877,11 +877,11 @@ export function templateToTypescript( mapper.text('__glintDSL__.applyAttributes('); - // We map the `__glintY__.element` arg to the first attribute node, which has the effect + // We map the `__glintY__` arg to the first attribute node, which has the effect // such that diagnostics due to passing attributes to invalid elements will show up // on the attribute, rather than on the whole element. mapper.forNode(attr, () => { - mapper.text('__glintY__.element'); + mapper.text('__glintY__'); }); mapper.text(', {'); diff --git a/packages/template/-private/dsl/elements.d.ts b/packages/template/-private/dsl/elements.d.ts index 92931c3e..d73e306b 100644 --- a/packages/template/-private/dsl/elements.d.ts +++ b/packages/template/-private/dsl/elements.d.ts @@ -4398,7 +4398,6 @@ interface GlintSvgElementAttributesMap { } global { -/* These are not all the elements, but they are the ones with types of their own, beyond HTMLElement/SVGElement */ interface GlintTagNameAttributesMap { ['a']: HTMLAnchorElementAttributes; ['area']: HTMLAreaElementAttributes; @@ -4456,7 +4455,6 @@ interface GlintTagNameAttributesMap { ['track']: HTMLTrackElementAttributes; ['ul']: HTMLUListElementAttributes; ['video']: HTMLVideoElementAttributes; - ['HTMLElement']: HTMLElementAttributes; ['a']: SVGAElementAttributes; ['animate']: SVGAnimateElementAttributes; ['animateMotion']: SVGAnimateMotionElementAttributes; @@ -4520,7 +4518,6 @@ interface GlintTagNameAttributesMap { ['tspan']: SVGTSpanElementAttributes; ['use']: SVGUseElementAttributes; ['view']: SVGViewElementAttributes; - ['SVGElement']: SVGElementAttributes; } } diff --git a/packages/template/__tests__/augmentation.test.ts b/packages/template/__tests__/augmentation.test.ts index 97fb5180..f6f413af 100644 --- a/packages/template/__tests__/augmentation.test.ts +++ b/packages/template/__tests__/augmentation.test.ts @@ -3,12 +3,9 @@ */ import '@glint/template'; -const BRAND = Symbol('AugmentedCustomElement brand'); - export class AugmentedCustomElement extends HTMLElement { declare propNum: number; declare propStr: string; - //declare static readonly [BRAND]: unique symbol; } export interface AugmentedCustomElementAttributes { diff --git a/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts b/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts index 79c66321..241b6829 100644 --- a/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts +++ b/test-packages/package-test-core/__tests__/transform/template-to-typescript.test.ts @@ -995,6 +995,36 @@ describe('Transform: rewriteTemplate', () => { }" `); }); + + test('with attributes', () => { + let template = stripIndent` + + `; + + expect(templateBody(template)).toMatchInlineSnapshot(` + "{ + const __glintY__ = __glintDSL__.emitElement("my-custom-element"); + __glintDSL__.applyAttributes(__glintY__.element, { + "prop-num": __glintDSL__.resolveOrReturn(__glintDSL__.Globals["str"])(), + + "prop-str": __glintDSL__.resolveOrReturn(__glintDSL__.Globals["two"])(), + + + }); + } + __glintRef__; __glintDSL__; + // begin directive placeholders + // @ts-expect-error expect-error + ; + // @ts-expect-error expect-error + ;" + `); + }); }); describe('angle bracket components', () => { diff --git a/test-packages/ts-template-imports-app/src/custom-elements.gts b/test-packages/ts-template-imports-app/src/custom-elements.gts index 1b05197d..ae320287 100644 --- a/test-packages/ts-template-imports-app/src/custom-elements.gts +++ b/test-packages/ts-template-imports-app/src/custom-elements.gts @@ -1,7 +1,7 @@ const two = 2; const str = "hello"; -type X = GlintCustomElements['my-custom-element']; +type X = GlintCustomElementsMap['my-custom-element']; export const UsesCustomElement =