From d93e698baf5babbbc143b5c6f2eaeff013e3f923 Mon Sep 17 00:00:00 2001 From: Matt Pharoah Date: Wed, 15 Feb 2023 09:37:48 -0500 Subject: [PATCH] Added comments to explain the Typescript metaprogramming --- src/internal/shoelace-element.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/internal/shoelace-element.ts b/src/internal/shoelace-element.ts index c0372b54e..664eae237 100644 --- a/src/internal/shoelace-element.ts +++ b/src/internal/shoelace-element.ts @@ -1,16 +1,21 @@ import { LitElement } from 'lit'; import { property } from 'lit/decorators.js'; +// Match event types that are registered on GlobalEventHandlersEventMap... type EventTypeRequiresDetail = T extends keyof GlobalEventHandlersEventMap - ? GlobalEventHandlersEventMap[T] extends CustomEvent> - ? GlobalEventHandlersEventMap[T] extends CustomEvent> + ? // ...where the event detail is an object... + GlobalEventHandlersEventMap[T] extends CustomEvent> + ? // ...that is non-empty... + GlobalEventHandlersEventMap[T] extends CustomEvent> ? never - : Partial extends GlobalEventHandlersEventMap[T]['detail'] + : // ...and has at least one non-optional property + Partial extends GlobalEventHandlersEventMap[T]['detail'] ? never : T : never : never; +// The inverse of the above (match any type that doesn't match EventTypeRequiresDetail) type EventTypeDoesNotRequireDetail = T extends keyof GlobalEventHandlersEventMap ? GlobalEventHandlersEventMap[T] extends CustomEvent> ? GlobalEventHandlersEventMap[T] extends CustomEvent> @@ -21,15 +26,22 @@ type EventTypeDoesNotRequireDetail = T extends keyof GlobalEventHandlersEvent : T : T; +// `keyof EventTypesWithRequiredDetail` lists all registered event types that require detail type EventTypesWithRequiredDetail = { [EventType in keyof GlobalEventHandlersEventMap as EventTypeRequiresDetail]: true; }; +// `keyof EventTypesWithRequiredDetail` lists all registered event types that do NOT require detail type EventTypesWithoutRequiredDetail = { [EventType in keyof GlobalEventHandlersEventMap as EventTypeDoesNotRequireDetail]: true; }; +// Helper to make a specific property of an object non-optional type WithRequired = T & { [P in K]-?: T[P] }; + +// Given an event name string, get a valid type for the options to initialize the event that is more restrictive +// than just CustomEventInit when appropriate (validate the type of the event detail, and require it to be +// provided if the event requires it) type SlEventInit = T extends keyof GlobalEventHandlersEventMap ? GlobalEventHandlersEventMap[T] extends CustomEvent> ? GlobalEventHandlersEventMap[T] extends CustomEvent> @@ -40,6 +52,7 @@ type SlEventInit = T extends keyof GlobalEventHandlersEventMap : CustomEventInit : CustomEventInit; +// Given an event name string, get the type of the event type GetCustomEventType = T extends keyof GlobalEventHandlersEventMap ? GlobalEventHandlersEventMap[T] extends CustomEvent ? GlobalEventHandlersEventMap[T]