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 =
+
+
+ {{!@glint-expect-error: swapped props}}
+
+;
\ 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 =
+
+
+ {{!@glint-expect-error: swapped props}}
+
+;
\ 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 =
-
+
{{!@glint-expect-error: swapped props}}
;
\ 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 =
+ {{! Baseline to test that the TS Plugin is working}}
+ Example
+
+ {{! @glint-expect-error: wrong type}}
+ Example
+
+ {{! Correct usage of custom element}}
- {{!@glint-expect-error: swapped props}}
+ {{! @glint-expect-error: swapped props}}
;
\ 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 =
- {{! Baseline to test that the TS Plugin is working}}
- Example
-
- {{! @glint-expect-error: wrong type}}
- Example
-
- {{! Correct usage of custom element}}
-
-
- {{! @glint-expect-error: swapped props}}
-
-;
\ 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 =
+ {{! defined in types/index.d.ts via Globals}}
+ {{t "hello"}}
+ {{! @glint-expect-error}}
+ {{t 223}}
+
- {{!@glint-expect-error: swapped props}}
-
-;
\ 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;
+ };
+ }
+}
+
+
+
+ hi
+
+
\ 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