Skip to content

Commit

Permalink
improve the type helper
Browse files Browse the repository at this point in the history
  • Loading branch information
trusktr committed Oct 27, 2024
1 parent 176c97f commit a3a639a
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 17 deletions.
5 changes: 5 additions & 0 deletions examples/kitchen-sink-vue/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ sink.value!.$emit

<!-- @vue-expect-error built-in attributes are type checked -->
<kitchen-sink spellcheck="123"></kitchen-sink>

<!-- @vue-expect-error technically .attr should accept strings because it is
setting an attribute, but the types are limited in this regard: the type is
the same regardless if .attr or .prop is used. -->
<kitchen-sink :foo.attr="'123'"></kitchen-sink>
</template>

<style scoped></style>
51 changes: 34 additions & 17 deletions examples/kitchen-sink-vue/src/KitchenSink.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {EmitFn, HTMLAttributes, ObjectEmitsOptions, PublicProps} from 'vue'
import {type EmitFn, type HTMLAttributes, type PublicProps} from 'vue'

// Select the properties to expose to template type checking.
type KitchenSinkAttributes = 'foo' | 'bar'
Expand All @@ -20,28 +20,45 @@ export class SomeEvent extends Event {
}
}

type DefineCustomElement<
T extends HTMLElement,
Attributes extends keyof T = keyof T,
Events extends ObjectEmitsOptions = {},
> = new () => T & {
// Use this to define the properties exposed to template type checking.
/** @deprecated Do not use the $props property on a Custom Element ref, this is for template prop types only. */
$props: HTMLAttributes & Partial<Pick<T, Attributes>> & PublicProps

// Use this to define specifically the event properties exposed to template type checking.
/** @deprecated Do not use the $emit property on a Custom Element ref, this is for template prop types only. */
$emit: EmitFn<Events>

// /** @deprecated Do not use the $attrs property on an element ref, this is for template prop types only. */
// $attrs: {[x: string]: string}
type KitchenSinkEvents = {
'some-event': SomeEvent
}

customElements.define('kitchen-sink', KitchenSink)

declare module 'vue' {
interface GlobalComponents {
// Use the helper to add any custom element to GlobalComponents.
'kitchen-sink': DefineCustomElement<KitchenSink, KitchenSinkAttributes, {'some-event': (e: SomeEvent) => void}>
'kitchen-sink': DefineCustomElement<KitchenSink, KitchenSinkEvents, KitchenSinkAttributes>
}
}

// helper for Vue type definitions /////////////////////////////////////////////////////////

type DefineCustomElement<
ElementType extends HTMLElement,
Events extends EventMap = {},
SelectedAttributes extends keyof ElementType = keyof ElementType,
> = new () => ElementType & {
// Use $props to define the properties exposed to template type checking. Vue
// specifically reads prop definitions from the `$props` type. Note that we
// combine the element's props with the global HTML props and Vue's special
// props.
/** @deprecated Do not use the $props property on a Custom Element ref, this is for template prop types only. */
$props: HTMLAttributes & Partial<Pick<ElementType, SelectedAttributes>> & PublicProps

// Use $emit to specifically define event types. Vue specifically reads event
// types from the `$emit` type. Note that `$emit` expects a particular format
// that we map `Events` to.
/** @deprecated Do not use the $emit property on a Custom Element ref, this is for template prop types only. */
$emit: VueEmit<Events>
}

type EventMap = {
[event: string]: Event
}

// This maps an EventMap to the format that Vue's $emit type expects.
type VueEmit<T extends EventMap> = EmitFn<{
[K in keyof T]: (event: T[K]) => void
}>

0 comments on commit a3a639a

Please sign in to comment.