CustomEvents and TypeScript
fgladisch opened this issue · comments
Hi!
We want to use CustomEvents with Haunted in TypeScript, but ran into a problem when defining "this" for a function:
// What is "this" here?!
function List(this: any, { items }: { items: { id: string, name: string }[] }) {
const handleListItemSelect = (id: string) => () => {
this.dispatchEvent(
new CustomEvent("item-change", {
bubbles: true,
composed: true,
detail: { id }
})
);
};
return html`
${items.map(
item => html`
<div @click=${handleListItemSelect(item.id)}>${item.name}</div>
`
)}
`;
}
Haunted does not seem to export anything we can replace "any" with. Any ideas?
Thanks!
I'm not a TS user but we have several around here, hopefully someone will chime in.
It's seems this
has to be Element
from lib.dom.d.ts
. But TypeScript gives an error when defining the custom element:
customElements.define("clinq-list", component<Props>(List));
TS2345: Argument of type '(this: Element, { items }: Props) => TemplateResult' is not assignable to parameter of type 'Renderer<Props>'.
The 'this' types of each signature are incompatible.
Type 'unknown' is not assignable to type 'Element'.
Haunted & Ts versions: "haunted": "^4.7.0", "typescript": "^3.7.2",
Same problem here, when I try to define a custom element like this:
export const wcExample = component(Example, {
useShadowDOM: false
});
customElements.define('example', wcExample);
I got this TS error
Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, dir, and 106 more.ts(2345)
any clue on theright way to declare a component on TS?
i have this:
import { html } from 'haunted';
import { TemplateResult } from 'lit-html';
import scss from './dsTitle.scss';
enum DsType {
t1 = "t1",
t2 = "t2",
}
export interface Props extends HTMLElement {
dsType: DsType;
}
export function dsTitle({dsType}: Props): TemplateResult {
return html`
<style>
${scss}
</style>
<header>
</header>
`;
}
and on the index.ts:
import { dsTitle, Props } from './dsTitle';
import { component } from 'haunted';
const options: any = { observedAttributes: ['ds-type'] };
window.customElements.define('ds-button', component<Props>(dsTitle, options));
how can avoid "any" on options? and, am i right on my TS definitions on haunted?
Anything new about that?
customElements.define('ds-button', component<Props>(dsTitle, { observedAttributes: ['ds-type'] }));
OR
const options: Record<'observedAttributes', Array<keyof Props>> = { observedAttributes: ['ds-type'] };
customElements.define('ds-button', component<Props>(dsTitle, options));
edit: for camelCase attribute you have to add two types :
export interface Props extends HTMLElement {
dsType: DsType;
ds-type: DsType;
}
it is the only way that I found to work, if you have better idea...
I might have some semi-decent answers here (maybe).
First, to address the original custom event issue -- I don't see a need to pass in a context. this
should simply refer to the Element that is created by List (assuming this is a custom element). I recreated the above scenario and this
worked just fine and appropriately referred to the element that fired the custom event. It was heard by a parent element, too, that was using this.addEventListener
. Maybe Haunted has changed since the issue was originally reported?
Second, for the more recent discussions about defining the custom element, I was able to give component()
a generic that is an Intersection type to satisfy TypeScript and the overloads for Renderer and Creator, which I feel is a bit better than interface Props extends HTMLElement
because it keeps the shape of the props object cleaner.
customElements.define('my-element', component<HTMLElement & Props>(MyElement));
or at worst,
customElements.define('my-element', component<HTMLElement & Props>(MyElement), { observedAttributes: ['foo-bar'] }));
#310 is a sloppy PR I made to illustrate this.
Follow-up: I didn't have "strict": true
on in my tsconfig
I solved by passing in this: unknown
, which is what GenericRenderer
expects, then casting this
to HTMLElement
in the places I needed it.
So in the original question it would be:
function List(this: unknown, { items }: { items: { id: string, name: string }[] }) {
const handleListItemSelect = (id: string) => () => {
(this as HTMLElement).dispatchEvent(
new CustomEvent("item-change", {
bubbles: true,
composed: true,
detail: { id }
})
);
};