From 2159adbce1ee940d9a6bf29ee3f6e1d58f1693ab Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 31 Mar 2026 00:14:03 +1100 Subject: [PATCH 1/2] fix: add defineSlots to all components for proper slot type inference All 19 components with slots were missing defineSlots, meaning slot props were typed as any. This adds proper type definitions so IDEs provide autocomplete and type checking for slot bindings. --- .../runtime/components/GoogleMaps/ScriptGoogleMaps.vue | 8 ++++++++ .../components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue | 4 ++++ .../components/GoogleMaps/ScriptGoogleMapsMarker.vue | 4 ++++ .../GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue | 5 +++++ .../components/GoogleMaps/ScriptGoogleMapsOverlayView.vue | 4 ++++ .../components/GoogleMaps/ScriptGoogleMapsStaticMap.vue | 3 +++ .../script/src/runtime/components/ScriptBlueskyEmbed.vue | 6 ++++++ .../script/src/runtime/components/ScriptCarbonAds.vue | 6 ++++++ packages/script/src/runtime/components/ScriptCrisp.vue | 7 +++++++ .../script/src/runtime/components/ScriptGoogleAdsense.vue | 6 ++++++ .../src/runtime/components/ScriptInstagramEmbed.vue | 6 ++++++ packages/script/src/runtime/components/ScriptIntercom.vue | 7 +++++++ .../script/src/runtime/components/ScriptLemonSqueezy.vue | 4 ++++ .../script/src/runtime/components/ScriptPayPalButtons.vue | 8 ++++++++ .../src/runtime/components/ScriptPayPalMessages.vue | 8 ++++++++ .../src/runtime/components/ScriptStripePricingTable.vue | 7 +++++++ .../script/src/runtime/components/ScriptVimeoPlayer.vue | 8 ++++++++ packages/script/src/runtime/components/ScriptXEmbed.vue | 6 ++++++ .../script/src/runtime/components/ScriptYouTubePlayer.vue | 8 ++++++++ 19 files changed, 115 insertions(+) diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue index 9171a170..9e89d418 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue @@ -94,6 +94,14 @@ const emits = defineEmits<{ error: [] }>() +defineSlots<{ + default?: () => any + placeholder?: () => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey const runtimeConfig = useRuntimeConfig() diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue index 3227486d..ada70a9c 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue @@ -52,6 +52,10 @@ const emit = defineEmits<{ zindex_changed: [] }>() +defineSlots<{ + default?: () => any +}>() + const infoWindowEvents = [ 'close', 'closeclick', diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue index be878a74..5ea8d52c 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue @@ -42,6 +42,10 @@ const emit = defineEmits<{ */ dragstart: [payload: google.maps.MapMouseEvent] }>() +defineSlots<{ + default?: () => any + content?: () => any +}>() const dragEvents = ['drag', 'dragend', 'dragstart'] as const const slots = useSlots() const markerContent = useTemplateRef('marker-content') diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue index aa026c55..310916af 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue @@ -69,6 +69,11 @@ const emit = defineEmits<{ clusteringend: [payload: MarkerClustererInstance] }>() +defineSlots<{ + default?: () => any + renderer?: (props: { cluster: Cluster, stats: ClusterStats, map: google.maps.Map }) => any +}>() + const markerClustererEvents = [ 'click', 'clusteringbegin', diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue index 1d1f447b..e988f2c3 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue @@ -64,6 +64,10 @@ const props = withDefaults(defineProps<{ hideWhenClustered: true, }) +defineSlots<{ + default?: () => any +}>() + const open = defineModel('open', { default: undefined }) const markerContext = inject(MARKER_INJECTION_KEY, undefined) diff --git a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue index ca9caf33..b890f3a2 100644 --- a/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +++ b/packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue @@ -107,6 +107,9 @@ const props = withDefaults(defineProps<{ height: 400, }) +defineSlots<{ + default?: (props: { src: string }) => any +}>() const runtimeConfig = useRuntimeConfig() const proxyConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleStaticMapsProxy const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey diff --git a/packages/script/src/runtime/components/ScriptBlueskyEmbed.vue b/packages/script/src/runtime/components/ScriptBlueskyEmbed.vue index 6ed96278..4893e403 100644 --- a/packages/script/src/runtime/components/ScriptBlueskyEmbed.vue +++ b/packages/script/src/runtime/components/ScriptBlueskyEmbed.vue @@ -29,6 +29,12 @@ const props = withDefaults(defineProps<{ imageProxyEndpoint: undefined, }) +defineSlots<{ + default?: (props: NonNullable) => any + loading?: () => any + error?: (props: { error: typeof error.value }) => any +}>() + const prefix = scriptsPrefix() const resolvedApiEndpoint = computed((): string => props.apiEndpoint || `${prefix}/embed/bluesky`) diff --git a/packages/script/src/runtime/components/ScriptCarbonAds.vue b/packages/script/src/runtime/components/ScriptCarbonAds.vue index e505f566..2658bd5a 100644 --- a/packages/script/src/runtime/components/ScriptCarbonAds.vue +++ b/packages/script/src/runtime/components/ScriptCarbonAds.vue @@ -19,6 +19,12 @@ const emit = defineEmits<{ ready: [HTMLScriptElement] }>() +defineSlots<{ + awaitingLoad?: () => any + loading?: () => any + error?: () => any +}>() + const attrId = `_carbonads_js` const carbonadsEl = ref(import.meta.client ? document.getElementById(attrId) : null) // syncs to useScript status diff --git a/packages/script/src/runtime/components/ScriptCrisp.vue b/packages/script/src/runtime/components/ScriptCrisp.vue index c1e8e50f..e6f5774a 100644 --- a/packages/script/src/runtime/components/ScriptCrisp.vue +++ b/packages/script/src/runtime/components/ScriptCrisp.vue @@ -26,6 +26,13 @@ const emits = defineEmits<{ error: [] }>() +defineSlots<{ + default?: (props: { ready: boolean }) => any + awaitingLoad?: () => any + loading?: () => any + error?: () => any +}>() + const rootEl = ref(null) const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl }) diff --git a/packages/script/src/runtime/components/ScriptGoogleAdsense.vue b/packages/script/src/runtime/components/ScriptGoogleAdsense.vue index 46dfc2be..7da2914d 100644 --- a/packages/script/src/runtime/components/ScriptGoogleAdsense.vue +++ b/packages/script/src/runtime/components/ScriptGoogleAdsense.vue @@ -27,6 +27,12 @@ const emits = defineEmits<{ error: [] }>() +defineSlots<{ + awaitingLoad?: () => any + loading?: () => any + error?: () => any +}>() + const rootEl = ref(null) const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl }) diff --git a/packages/script/src/runtime/components/ScriptInstagramEmbed.vue b/packages/script/src/runtime/components/ScriptInstagramEmbed.vue index 9387ad39..5e619070 100644 --- a/packages/script/src/runtime/components/ScriptInstagramEmbed.vue +++ b/packages/script/src/runtime/components/ScriptInstagramEmbed.vue @@ -29,6 +29,12 @@ const props = withDefaults(defineProps<{ apiEndpoint: undefined, }) +defineSlots<{ + default?: (props: { html: string, shortcode: string | undefined, postUrl: string }) => any + loading?: () => any + error?: (props: { error: typeof error.value }) => any +}>() + const prefix = scriptsPrefix() const apiEndpoint = computed(() => props.apiEndpoint || `${prefix}/embed/instagram`) diff --git a/packages/script/src/runtime/components/ScriptIntercom.vue b/packages/script/src/runtime/components/ScriptIntercom.vue index 53e95be6..e68969a7 100644 --- a/packages/script/src/runtime/components/ScriptIntercom.vue +++ b/packages/script/src/runtime/components/ScriptIntercom.vue @@ -28,6 +28,13 @@ const emits = defineEmits<{ error: [] }>() +defineSlots<{ + default?: (props: { ready: boolean }) => any + awaitingLoad?: () => any + loading?: () => any + error?: () => any +}>() + const rootEl = ref(null) const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl }) diff --git a/packages/script/src/runtime/components/ScriptLemonSqueezy.vue b/packages/script/src/runtime/components/ScriptLemonSqueezy.vue index 674abae0..56a43f7b 100644 --- a/packages/script/src/runtime/components/ScriptLemonSqueezy.vue +++ b/packages/script/src/runtime/components/ScriptLemonSqueezy.vue @@ -16,6 +16,10 @@ const emits = defineEmits<{ lemonSqueezyEvent: [LemonSqueezyEventPayload] }>() +defineSlots<{ + default?: () => any +}>() + const rootEl = ref(null) const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl }) const instance = useScriptLemonSqueezy({ diff --git a/packages/script/src/runtime/components/ScriptPayPalButtons.vue b/packages/script/src/runtime/components/ScriptPayPalButtons.vue index 0d1755c8..8bf3a894 100644 --- a/packages/script/src/runtime/components/ScriptPayPalButtons.vue +++ b/packages/script/src/runtime/components/ScriptPayPalButtons.vue @@ -68,6 +68,14 @@ const emit = defineEmits<{ error: [error: unknown] }>() +defineSlots<{ + default?: (props: { sdkInstance: SdkInstance | undefined }) => any + placeholder?: () => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + const el = ref(null) const rootEl = ref(null) const ready = ref(false) diff --git a/packages/script/src/runtime/components/ScriptPayPalMessages.vue b/packages/script/src/runtime/components/ScriptPayPalMessages.vue index 55b455f8..c5893c4c 100644 --- a/packages/script/src/runtime/components/ScriptPayPalMessages.vue +++ b/packages/script/src/runtime/components/ScriptPayPalMessages.vue @@ -69,6 +69,14 @@ const emit = defineEmits<{ error: [error: unknown] }>() +defineSlots<{ + default?: (props: { messagesSession: PayPalMessagesSession | undefined }) => any + placeholder?: () => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + const el = ref(null) const rootEl = ref(null) const ready = ref(false) diff --git a/packages/script/src/runtime/components/ScriptStripePricingTable.vue b/packages/script/src/runtime/components/ScriptStripePricingTable.vue index 35fb4697..6d9b8a3a 100644 --- a/packages/script/src/runtime/components/ScriptStripePricingTable.vue +++ b/packages/script/src/runtime/components/ScriptStripePricingTable.vue @@ -20,6 +20,13 @@ const emit = defineEmits<{ error: [] }>() +defineSlots<{ + default?: () => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + const rootEl = ref() const containerEl = ref() const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl }) diff --git a/packages/script/src/runtime/components/ScriptVimeoPlayer.vue b/packages/script/src/runtime/components/ScriptVimeoPlayer.vue index 9fa2cb46..4646672e 100644 --- a/packages/script/src/runtime/components/ScriptVimeoPlayer.vue +++ b/packages/script/src/runtime/components/ScriptVimeoPlayer.vue @@ -67,6 +67,14 @@ const props = withDefaults(defineProps<{ const emits = defineEmits() +defineSlots<{ + default?: () => any + placeholder?: (props: { placeholder: string | undefined }) => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + type EventMap = [event: Vimeo.EventMap[E], player: Vimeo] interface TEmits { diff --git a/packages/script/src/runtime/components/ScriptXEmbed.vue b/packages/script/src/runtime/components/ScriptXEmbed.vue index aa01d630..d831dc58 100644 --- a/packages/script/src/runtime/components/ScriptXEmbed.vue +++ b/packages/script/src/runtime/components/ScriptXEmbed.vue @@ -28,6 +28,12 @@ const props = withDefaults(defineProps<{ imageProxyEndpoint: undefined, }) +defineSlots<{ + default?: (props: NonNullable) => any + loading?: () => any + error?: (props: { error: typeof error.value }) => any +}>() + const prefix = scriptsPrefix() const apiEndpoint = computed(() => props.apiEndpoint || `${prefix}/embed/x`) diff --git a/packages/script/src/runtime/components/ScriptYouTubePlayer.vue b/packages/script/src/runtime/components/ScriptYouTubePlayer.vue index 0c4a8fdc..c5ff0671 100644 --- a/packages/script/src/runtime/components/ScriptYouTubePlayer.vue +++ b/packages/script/src/runtime/components/ScriptYouTubePlayer.vue @@ -72,6 +72,14 @@ const emits = defineEmits<{ 'error': [e: YT.OnErrorEvent, target: YT.Player] 'api-change': [e: YT.PlayerEvent, target: YT.Player] }>() +defineSlots<{ + default?: () => any + placeholder?: (props: { placeholder: string }) => any + loading?: () => any + awaitingLoad?: () => any + error?: () => any +}>() + const events: (keyof YT.Events)[] = [ 'onReady', 'onStateChange', From d02a4da0cf905898e8c7088096d69c74748fc4f4 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 31 Mar 2026 00:26:44 +1100 Subject: [PATCH 2/2] feat: extract defineSlots in registry types generator Adds slot extraction to generate-registry-types.ts, producing {Component}Slots declarations and schema fields. Updates ScriptTypes.vue to render a Slots tab alongside Props and Events. --- packages/script/src/registry-types.json | 421 ++++++++++++++++++++++++ scripts/generate-registry-types.ts | 51 +++ 2 files changed, 472 insertions(+) diff --git a/packages/script/src/registry-types.json b/packages/script/src/registry-types.json index a185f659..fb728872 100644 --- a/packages/script/src/registry-types.json +++ b/packages/script/src/registry-types.json @@ -27,6 +27,11 @@ "name": "ScriptBlueskyEmbedProps", "kind": "interface", "code": "interface ScriptBlueskyEmbedProps {\n /**\n * The Bluesky post URL to embed\n * @example 'https://bsky.app/profile/bsky.app/post/3mgnwwvj3u22a'\n */\n postUrl: string\n /**\n * Custom API endpoint for fetching post data\n */\n apiEndpoint?: string\n /**\n * Custom image proxy endpoint\n */\n imageProxyEndpoint?: string\n /**\n * Root element attributes\n */\n rootAttrs?: HTMLAttributes\n}" + }, + { + "name": "ScriptBlueskyEmbedSlots", + "kind": "interface", + "code": "interface ScriptBlueskyEmbedSlots {\n default?: (props: object) => any\n loading?: () => any\n error?: (props: object) => any\n}" } ], "clarity": [ @@ -78,6 +83,11 @@ "name": "ScriptCrispEvents", "kind": "interface", "code": "interface ScriptCrispEvents {\n ready: ReturnType\n error: -\n}" + }, + { + "name": "ScriptCrispSlots", + "kind": "interface", + "code": "interface ScriptCrispSlots {\n default?: (props: { ready: boolean }) => any\n awaitingLoad?: () => any\n loading?: () => any\n error?: () => any\n}" } ], "databuddy-analytics": [ @@ -124,6 +134,11 @@ "name": "ScriptGoogleAdsenseEvents", "kind": "interface", "code": "interface ScriptGoogleAdsenseEvents {\n ready: ReturnType\n error: -\n}" + }, + { + "name": "ScriptGoogleAdsenseSlots", + "kind": "interface", + "code": "interface ScriptGoogleAdsenseSlots {\n awaitingLoad?: () => any\n loading?: () => any\n error?: () => any\n}" } ], "google-analytics": [ @@ -209,6 +224,11 @@ "kind": "interface", "code": "interface ScriptGoogleMapsEvents {\n ready: typeof googleMaps\n error: -\n}" }, + { + "name": "ScriptGoogleMapsSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsSlots {\n default?: () => any\n placeholder?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" + }, { "name": "ScriptGoogleMapsCircleProps", "kind": "interface", @@ -244,6 +264,11 @@ "kind": "interface", "code": "interface ScriptGoogleMapsInfoWindowEvents {\n close: -\n closeclick: -\n content_changed: -\n domready: -\n headercontent_changed: -\n headerdisabled_changed: -\n position_changed: -\n visible: -\n zindex_changed: -\n}" }, + { + "name": "ScriptGoogleMapsInfoWindowSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsInfoWindowSlots {\n default?: () => any\n}" + }, { "name": "ScriptGoogleMapsMarkerProps", "kind": "interface", @@ -254,6 +279,11 @@ "kind": "interface", "code": "interface ScriptGoogleMapsMarkerEvents {\n click: google.maps.MapMouseEvent\n drag: google.maps.MapMouseEvent\n dragend: google.maps.MapMouseEvent\n dragstart: google.maps.MapMouseEvent\n}" }, + { + "name": "ScriptGoogleMapsMarkerSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsMarkerSlots {\n default?: () => any\n content?: () => any\n}" + }, { "name": "ScriptGoogleMapsMarkerClustererProps", "kind": "interface", @@ -264,11 +294,21 @@ "kind": "interface", "code": "interface ScriptGoogleMapsMarkerClustererEvents {\n click: MarkerClustererInstance\n clusteringbegin: MarkerClustererInstance\n clusteringend: MarkerClustererInstance\n}" }, + { + "name": "ScriptGoogleMapsMarkerClustererSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsMarkerClustererSlots {\n default?: () => any\n renderer?: (props: { cluster: Cluster, stats: ClusterStats, map: google.maps.Map }) => any\n}" + }, { "name": "ScriptGoogleMapsOverlayViewProps", "kind": "interface", "code": "interface ScriptGoogleMapsOverlayViewProps {\n /**\n * Geographic position for the overlay. Falls back to parent marker position if omitted.\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView\n */\n position?: google.maps.LatLngLiteral\n /**\n * Anchor point of the overlay relative to its position.\n * @default 'bottom-center'\n */\n anchor?: OverlayAnchor\n /**\n * Pixel offset from the anchor position.\n */\n offset?: { x: number, y: number }\n /**\n * The map pane on which to render the overlay.\n * @default 'floatPane'\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#MapPanes\n */\n pane?: OverlayPane\n /**\n * CSS z-index for the overlay element.\n */\n zIndex?: number\n /**\n * Whether to block map click and gesture events from passing through the overlay.\n * @default true\n */\n blockMapInteraction?: boolean\n /**\n * Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior.\n * Set to `true` for default 40px padding, or a number for custom padding.\n * @default true\n */\n panOnOpen?: boolean | number\n /**\n * Automatically hide the overlay when its parent marker joins a cluster (on zoom out).\n * Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.\n * @default true\n */\n hideWhenClustered?: boolean\n}" }, + { + "name": "ScriptGoogleMapsOverlayViewSlots", + "kind": "interface", + "code": "interface ScriptGoogleMapsOverlayViewSlots {\n default?: () => any\n}" + }, { "name": "ScriptGoogleMapsPolygonProps", "kind": "interface", @@ -440,6 +480,11 @@ "name": "ScriptInstagramEmbedProps", "kind": "interface", "code": "interface ScriptInstagramEmbedProps {\n /**\n * The Instagram post URL to embed\n * e.g., https://www.instagram.com/p/ABC123/\n */\n postUrl: string\n /**\n * Whether to include captions in the embed\n * @default true\n */\n captions?: boolean\n /**\n * Custom API endpoint for fetching embed HTML\n */\n apiEndpoint?: string\n /**\n * Root element attributes\n */\n rootAttrs?: HTMLAttributes\n}" + }, + { + "name": "ScriptInstagramEmbedSlots", + "kind": "interface", + "code": "interface ScriptInstagramEmbedSlots {\n default?: (props: { html: string, shortcode: string | undefined, postUrl: string }) => any\n loading?: () => any\n error?: (props: object) => any\n}" } ], "intercom": [ @@ -462,6 +507,11 @@ "name": "ScriptIntercomEvents", "kind": "interface", "code": "interface ScriptIntercomEvents {\n ready: ReturnType\n error: -\n}" + }, + { + "name": "ScriptIntercomSlots", + "kind": "interface", + "code": "interface ScriptIntercomSlots {\n default?: (props: { ready: boolean }) => any\n awaitingLoad?: () => any\n loading?: () => any\n error?: () => any\n}" } ], "lemon-squeezy": [ @@ -484,6 +534,11 @@ "name": "ScriptLemonSqueezyEvents", "kind": "interface", "code": "interface ScriptLemonSqueezyEvents {\n ready: ReturnType\n lemonSqueezyEvent: LemonSqueezyEventPayload\n}" + }, + { + "name": "ScriptLemonSqueezySlots", + "kind": "interface", + "code": "interface ScriptLemonSqueezySlots {\n default?: () => any\n}" } ], "matomo-analytics": [ @@ -580,6 +635,11 @@ "kind": "interface", "code": "interface ScriptPayPalButtonsEvents {\n ready: SdkInstance\n error: unknown\n}" }, + { + "name": "ScriptPayPalButtonsSlots", + "kind": "interface", + "code": "interface ScriptPayPalButtonsSlots {\n default?: (props: { sdkInstance: SdkInstance | undefined }) => any\n placeholder?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" + }, { "name": "ScriptPayPalMessagesProps", "kind": "interface", @@ -589,6 +649,11 @@ "name": "ScriptPayPalMessagesEvents", "kind": "interface", "code": "interface ScriptPayPalMessagesEvents {\n ready: PayPalMessagesSession\n error: unknown\n}" + }, + { + "name": "ScriptPayPalMessagesSlots", + "kind": "interface", + "code": "interface ScriptPayPalMessagesSlots {\n default?: (props: { messagesSession: PayPalMessagesSession | undefined }) => any\n placeholder?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" } ], "plausible-analytics": [ @@ -738,6 +803,11 @@ "name": "ScriptStripePricingTableEvents", "kind": "interface", "code": "interface ScriptStripePricingTableEvents {\n ready: ReturnType\n error: -\n}" + }, + { + "name": "ScriptStripePricingTableSlots", + "kind": "interface", + "code": "interface ScriptStripePricingTableSlots {\n default?: () => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" } ], "tiktok-pixel": [ @@ -831,6 +901,11 @@ "name": "ScriptVimeoPlayerProps", "kind": "interface", "code": "interface ScriptVimeoPlayerProps {\n // custom\n trigger?: ElementScriptTrigger\n placeholderAttrs?: ImgHTMLAttributes\n rootAttrs?: HTMLAttributes\n aboveTheFold?: boolean\n vimeoOptions?: VimeoOptions\n id?: number | undefined\n url?: string | undefined\n ratio?: string\n /**\n * Object-fit for the placeholder image.\n *\n * @default 'contain'\n */\n placeholderObjectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n}" + }, + { + "name": "ScriptVimeoPlayerSlots", + "kind": "interface", + "code": "interface ScriptVimeoPlayerSlots {\n default?: () => any\n placeholder?: (props: { placeholder: string | undefined }) => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" } ], "x-embed": [ @@ -848,6 +923,11 @@ "name": "ScriptXEmbedProps", "kind": "interface", "code": "interface ScriptXEmbedProps {\n /**\n * The tweet ID to embed\n */\n tweetId: string\n /**\n * Custom API endpoint for fetching tweet data\n */\n apiEndpoint?: string\n /**\n * Custom image proxy endpoint\n */\n imageProxyEndpoint?: string\n /**\n * Root element attributes\n */\n rootAttrs?: HTMLAttributes\n}" + }, + { + "name": "ScriptXEmbedSlots", + "kind": "interface", + "code": "interface ScriptXEmbedSlots {\n default?: (props: object) => any\n loading?: () => any\n error?: (props: object) => any\n}" } ], "x-pixel": [ @@ -892,6 +972,11 @@ "name": "ScriptYouTubePlayerEvents", "kind": "interface", "code": "interface ScriptYouTubePlayerEvents {\n ready: YT.PlayerEvent\n state-change: YT.OnStateChangeEvent\n playback-quality-change: YT.OnPlaybackQualityChangeEvent\n playback-rate-change: YT.OnPlaybackRateChangeEvent\n error: YT.OnErrorEvent\n api-change: YT.PlayerEvent\n}" + }, + { + "name": "ScriptYouTubePlayerSlots", + "kind": "interface", + "code": "interface ScriptYouTubePlayerSlots {\n default?: () => any\n placeholder?: (props: { placeholder: string }) => any\n loading?: () => any\n awaitingLoad?: () => any\n error?: () => any\n}" } ], "carbon-ads": [ @@ -904,6 +989,11 @@ "name": "ScriptCarbonAdsEvents", "kind": "interface", "code": "interface ScriptCarbonAdsEvents {\n error: string | Event\n ready: HTMLScriptElement\n}" + }, + { + "name": "ScriptCarbonAdsSlots", + "kind": "interface", + "code": "interface ScriptCarbonAdsSlots {\n awaitingLoad?: () => any\n loading?: () => any\n error?: () => any\n}" } ] }, @@ -2149,6 +2239,33 @@ "description": "Fired when the Google Maps script fails to load." } ], + "ScriptGoogleMapsSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "placeholder", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptGoogleMapsCircleProps": [ { "name": "options", @@ -2403,6 +2520,13 @@ "description": "Fired when the z-index of the info window changes." } ], + "ScriptGoogleMapsInfoWindowSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], "ScriptGoogleMapsMarkerProps": [ { "name": "position", @@ -2443,6 +2567,18 @@ "description": "Fired when the user starts dragging the marker." } ], + "ScriptGoogleMapsMarkerSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "content", + "type": "-", + "required": false + } + ], "ScriptGoogleMapsMarkerClustererProps": [ { "name": "options", @@ -2471,6 +2607,18 @@ "description": "Fired when the clusterer finishes clustering markers." } ], + "ScriptGoogleMapsMarkerClustererSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "renderer", + "type": "{ cluster: Cluster, stats: ClusterStats, map: google.maps.Map }", + "required": false + } + ], "ScriptGoogleMapsOverlayViewProps": [ { "name": "position", @@ -2531,6 +2679,13 @@ "required": false } ], + "ScriptGoogleMapsOverlayViewSlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], "ScriptGoogleMapsPolygonProps": [ { "name": "options", @@ -2791,6 +2946,23 @@ "description": "Root element attributes" } ], + "ScriptBlueskyEmbedSlots": [ + { + "name": "default", + "type": "object", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "object", + "required": false + } + ], "ScriptCarbonAdsProps": [ { "name": "serve", @@ -2826,6 +2998,23 @@ "required": false } ], + "ScriptCarbonAdsSlots": [ + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptCrispProps": [ { "name": "trigger", @@ -2871,6 +3060,28 @@ "required": false } ], + "ScriptCrispSlots": [ + { + "name": "default", + "type": "{ ready: boolean }", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptGoogleAdsenseProps": [ { "name": "dataAdClient", @@ -2916,6 +3127,23 @@ "required": false } ], + "ScriptGoogleAdsenseSlots": [ + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptInstagramEmbedProps": [ { "name": "postUrl", @@ -2943,6 +3171,23 @@ "description": "Root element attributes" } ], + "ScriptInstagramEmbedSlots": [ + { + "name": "default", + "type": "{ html: string, shortcode: string | undefined, postUrl: string }", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "object", + "required": false + } + ], "ScriptIntercomProps": [ { "name": "appId", @@ -3003,6 +3248,28 @@ "required": false } ], + "ScriptIntercomSlots": [ + { + "name": "default", + "type": "{ ready: boolean }", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptLemonSqueezyProps": [ { "name": "trigger", @@ -3022,6 +3289,13 @@ "required": false } ], + "ScriptLemonSqueezySlots": [ + { + "name": "default", + "type": "-", + "required": false + } + ], "ScriptPayPalButtonsProps": [ { "name": "rootAttrs", @@ -3097,6 +3371,33 @@ "required": false } ], + "ScriptPayPalButtonsSlots": [ + { + "name": "default", + "type": "{ sdkInstance: SdkInstance | undefined }", + "required": false + }, + { + "name": "placeholder", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptPayPalMessagesProps": [ { "name": "rootAttrs", @@ -3171,6 +3472,33 @@ "required": false } ], + "ScriptPayPalMessagesSlots": [ + { + "name": "default", + "type": "{ messagesSession: PayPalMessagesSession | undefined }", + "required": false + }, + { + "name": "placeholder", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptStripePricingTableProps": [ { "name": "trigger", @@ -3215,6 +3543,28 @@ "required": false } ], + "ScriptStripePricingTableSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptVimeoPlayerProps": [ { "name": "trigger", @@ -3264,6 +3614,33 @@ "defaultValue": "'contain'" } ], + "ScriptVimeoPlayerSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "placeholder", + "type": "{ placeholder: string | undefined }", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } + ], "ScriptXEmbedProps": [ { "name": "tweetId", @@ -3290,6 +3667,23 @@ "description": "Root element attributes" } ], + "ScriptXEmbedSlots": [ + { + "name": "default", + "type": "object", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "object", + "required": false + } + ], "ScriptYouTubePlayerProps": [ { "name": "placeholderAttrs", @@ -3397,6 +3791,33 @@ "type": "YT.PlayerEvent", "required": false } + ], + "ScriptYouTubePlayerSlots": [ + { + "name": "default", + "type": "-", + "required": false + }, + { + "name": "placeholder", + "type": "{ placeholder: string }", + "required": false + }, + { + "name": "loading", + "type": "-", + "required": false + }, + { + "name": "awaitingLoad", + "type": "-", + "required": false + }, + { + "name": "error", + "type": "-", + "required": false + } ] } } diff --git a/scripts/generate-registry-types.ts b/scripts/generate-registry-types.ts index 08e6f048..13dc5f98 100644 --- a/scripts/generate-registry-types.ts +++ b/scripts/generate-registry-types.ts @@ -263,6 +263,7 @@ interface ComponentMeta { fields: SchemaFieldMeta[] events: SchemaFieldMeta[] models: SchemaFieldMeta[] + slots: SchemaFieldMeta[] } function resolveTSType(node: any, source: string): string { @@ -340,6 +341,7 @@ function extractComponentMeta(scriptSource: string, fileName: string): Component let propsResult: { code: string, defaults: Record, fields: SchemaFieldMeta[] } | null = null const events: SchemaFieldMeta[] = [] const models: SchemaFieldMeta[] = [] + const slots: SchemaFieldMeta[] = [] const constArrays: Record = {} // First pass: collect `as const` arrays for event name resolution @@ -480,6 +482,44 @@ function extractComponentMeta(scriptSource: string, fileName: string): Component return } + // defineSlots<{...}>() + if (node.callee?.name === 'defineSlots') { + const typeArg = node.typeArguments?.params?.[0] + if (typeArg?.type === 'TSTypeLiteral') { + const slotsCode = scriptSource.slice(typeArg.start, typeArg.end) + const slotsComments = parseSchemaComments(slotsCode) + + for (const member of typeArg.members || []) { + if (member.type !== 'TSPropertySignature') + continue + const slotName = member.key?.name || member.key?.value + if (!slotName) + continue + + // Extract slot props type from the function signature: (props: { ... }) => any + let propsType = '-' + const fnType = member.typeAnnotation?.typeAnnotation + if (fnType?.type === 'TSFunctionType' && fnType.params?.length) { + const param = fnType.params[0] + const paramType = param?.typeAnnotation?.typeAnnotation + if (paramType) { + const resolved = resolveTSType(paramType, scriptSource) + // Avoid leaking unresolvable `typeof` references from runtime variables + propsType = resolved.includes('typeof') ? 'object' : resolved + } + } + + slots.push({ + name: slotName, + type: propsType, + required: !member.optional, + description: slotsComments[slotName]?.description, + }) + } + } + return + } + // defineProps / withDefaults(defineProps) let definePropsCall: any = null let defaultsObj: any = null @@ -532,6 +572,7 @@ function extractComponentMeta(scriptSource: string, fileName: string): Component fields: [...propsResult.fields, ...models], events, models, + slots, } } @@ -649,6 +690,16 @@ for (const [componentName, meta] of Object.entries(componentMetas)) { code: `interface ${componentName}Events {\n${meta.events.map(e => ` ${e.name}: ${e.type}`).join('\n')}\n}`, }) } + + // Store slots as schema fields under a separate key + if (meta.slots.length) { + schemaFields[`${componentName}Slots`] = meta.slots + types[slug].push({ + name: `${componentName}Slots`, + kind: 'interface', + code: `interface ${componentName}Slots {\n${meta.slots.map(s => ` ${s.name}${s.required ? '' : '?'}: ${s.type === '-' ? '() => any' : `(props: ${s.type}) => any`}`).join('\n')}\n}`, + }) + } } const output = {