@@ -250,6 +250,23 @@ function hasProvenance(version: PackumentVersion | null): boolean {
250250}
251251
252252const selectedPM = useSelectedPackageManager ()
253+ const { settings } = useSettings ()
254+
255+ // Fetch package analysis for @types info
256+ const { data : packageAnalysis } = usePackageAnalysis (packageName , requestedVersion )
257+
258+ // Get @types package name if available (non-deprecated)
259+ const typesPackageName = computed (() => {
260+ if (! packageAnalysis .value ) return null
261+ if (packageAnalysis .value .types .kind !== ' @types' ) return null
262+ if (packageAnalysis .value .types .deprecated ) return null
263+ return packageAnalysis .value .types .packageName
264+ })
265+
266+ // Check if we should show @types in install command
267+ const showTypesInInstall = computed (() => {
268+ return settings .value .includeTypesInInstall && typesPackageName .value
269+ })
253270
254271const installCommandParts = computed (() => {
255272 if (! pkg .value ) return []
@@ -271,11 +288,48 @@ const installCommand = computed(() => {
271288 })
272289})
273290
291+ // Get the dev dependency flag for the selected package manager
292+ function getDevFlag(pmId : string ): string {
293+ // bun uses lowercase -d, all others use -D
294+ return pmId === ' bun' ? ' -d' : ' -D'
295+ }
296+
297+ // @types install command parts (for display)
298+ const typesInstallCommandParts = computed (() => {
299+ if (! typesPackageName .value ) return []
300+ const pm = packageManagers .find (p => p .id === selectedPM .value )
301+ if (! pm ) return []
302+
303+ const devFlag = getDevFlag (selectedPM .value )
304+ const pkgSpec =
305+ selectedPM .value === ' deno' ? ` npm:${typesPackageName .value } ` : typesPackageName .value
306+
307+ return [pm .label , pm .action , devFlag , pkgSpec ]
308+ })
309+
310+ // Full install command including @types (for copying)
311+ const fullInstallCommand = computed (() => {
312+ if (! installCommand .value ) return ' '
313+ if (! showTypesInInstall .value || ! typesPackageName .value ) {
314+ return installCommand .value
315+ }
316+
317+ const pm = packageManagers .find (p => p .id === selectedPM .value )
318+ if (! pm ) return installCommand .value
319+
320+ const devFlag = getDevFlag (selectedPM .value )
321+ const pkgSpec =
322+ selectedPM .value === ' deno' ? ` npm:${typesPackageName .value } ` : typesPackageName .value
323+
324+ // Use semicolon to separate commands
325+ return ` ${installCommand .value }; ${pm .label } ${pm .action } ${devFlag } ${pkgSpec } `
326+ })
327+
274328// Copy install command
275329const copied = ref (false )
276330async function copyInstallCommand() {
277- if (! installCommand .value ) return
278- await navigator .clipboard .writeText (installCommand .value )
331+ if (! fullInstallCommand .value ) return
332+ await navigator .clipboard .writeText (fullInstallCommand .value )
279333 copied .value = true
280334 setTimeout (() => (copied .value = false ), 2000 )
281335}
@@ -453,6 +507,7 @@ defineOgImageComponent('Package', {
453507 <button
454508 type =" button"
455509 class =" font-mono text-xs text-fg-muted hover:text-fg bg-bg px-1 transition-colors duration-200 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
510+ aria-label =" Show full description"
456511 @click =" descriptionExpanded = true"
457512 >
458513 show more
@@ -719,7 +774,7 @@ defineOgImageComponent('Package', {
719774 :key =" pm.id"
720775 role =" tab"
721776 :aria-selected =" selectedPM === pm.id"
722- class =" px-2 py-1 font-mono text-xs rounded transition-all duration-150"
777+ class =" px-2 py-1 font-mono text-xs rounded transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 "
723778 :class ="
724779 selectedPM === pm.id
725780 ? 'bg-bg-elevated text-fg'
@@ -750,29 +805,54 @@ defineOgImageComponent('Package', {
750805 <span class =" w-2.5 h-2.5 rounded-full bg-[#333]" />
751806 <span class =" w-2.5 h-2.5 rounded-full bg-[#333]" />
752807 </div >
753- <div class =" flex items-center gap-2 px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4" >
754- <span class =" text-fg-subtle font-mono text-sm select-none" >$</span >
755- <code class =" font-mono text-sm"
756- ><ClientOnly
808+ <div class =" space-y-1 px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4" >
809+ <!-- Main package install -->
810+ <div class =" flex items-center gap-2" >
811+ <span class =" text-fg-subtle font-mono text-sm select-none" >$</span >
812+ <code class =" font-mono text-sm"
813+ ><ClientOnly
814+ ><span
815+ v-for =" (part, i) in installCommandParts"
816+ :key =" i"
817+ :class =" i === 0 ? 'text-fg' : 'text-fg-muted'"
818+ >{{ i > 0 ? ' ' : '' }}{{ part }}</span
819+ ><template #fallback
820+ ><span class =" text-fg" >npm</span
821+ ><span class =" text-fg-muted" > install {{ pkg.name }}</span ></template
822+ ></ClientOnly
823+ ></code
824+ >
825+ </div >
826+ <!-- @types package install (when enabled) -->
827+ <div v-if =" showTypesInInstall" class =" flex items-center gap-2" >
828+ <span class =" text-fg-subtle font-mono text-sm select-none" >$</span >
829+ <code class =" font-mono text-sm"
757830 ><span
758- v-for =" (part, i) in installCommandParts "
831+ v-for =" (part, i) in typesInstallCommandParts "
759832 :key =" i"
760833 :class =" i === 0 ? 'text-fg' : 'text-fg-muted'"
761834 >{{ i > 0 ? ' ' : '' }}{{ part }}</span
762- ><template #fallback
763- ><span class =" text-fg" >npm</span
764- ><span class =" text-fg-muted" > install {{ pkg.name }}</span ></template
765- ></ClientOnly
766- ></code
767- >
835+ ></code
836+ >
837+ <NuxtLink
838+ v-if =" typesPackageName"
839+ :to =" `/${typesPackageName}`"
840+ class =" text-fg-subtle hover:text-fg-muted text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
841+ title =" View @types package"
842+ >
843+ <span class =" i-carbon-arrow-right w-3 h-3" aria-hidden =" true" />
844+ <span class =" sr-only" >View {{ typesPackageName }}</span >
845+ </NuxtLink >
846+ </div >
768847 </div >
769848 </div >
770849 <button
771850 type =" button"
772851 class =" absolute top-3 right-3 px-2 py-1 font-mono text-xs text-fg-muted bg-bg-subtle/80 border border-border rounded transition-colors duration-200 hover:(text-fg border-border-hover) active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
852+ aria-label =" Copy install command"
773853 @click =" copyInstallCommand"
774854 >
775- {{ copied ? 'copied!' : 'copy' }}
855+ < span aria-live = " polite " > {{ copied ? 'copied!' : 'copy' }}</ span >
776856 </button >
777857 </div >
778858 </section >
0 commit comments