11<script setup lang="ts">
2+ import { NO_DEPENDENCY_ID } from ' ~/composables/usePackageComparison'
3+
24const packages = defineModel <string []>({ required: true })
35
46const props = defineProps <{
@@ -17,6 +19,29 @@ const { data: searchData, status } = useNpmSearch(inputValue, { size: 15 })
1719
1820const isSearching = computed (() => status .value === ' pending' )
1921
22+ // Trigger strings for "What Would James Do?" typeahead Easter egg
23+ // Intentionally not localized
24+ const EASTER_EGG_TRIGGERS = new Set ([
25+ ' no dep' ,
26+ ' none' ,
27+ ' vanilla' ,
28+ ' diy' ,
29+ ' zero' ,
30+ ' nothing' ,
31+ ' 0' ,
32+ " don't" ,
33+ ' native' ,
34+ ' use the platform' ,
35+ ])
36+
37+ // Check if "no dependency" option should show in typeahead
38+ const showNoDependencyOption = computed (() => {
39+ if (packages .value .includes (NO_DEPENDENCY_ID )) return false
40+ const input = inputValue .value .toLowerCase ().trim ()
41+ if (! input ) return false
42+ return EASTER_EGG_TRIGGERS .has (input )
43+ })
44+
2045// Filter out already selected packages
2146const filteredResults = computed (() => {
2247 if (! searchData .value ?.objects ) return []
@@ -32,7 +57,16 @@ function addPackage(name: string) {
3257 if (packages .value .length >= maxPackages .value ) return
3358 if (packages .value .includes (name )) return
3459
35- packages .value = [... packages .value , name ]
60+ // Keep NO_DEPENDENCY_ID always last
61+ if (name === NO_DEPENDENCY_ID ) {
62+ packages .value = [... packages .value , name ]
63+ } else if (packages .value .includes (NO_DEPENDENCY_ID )) {
64+ // Insert before the no-dep entry
65+ const withoutNoDep = packages .value .filter (p => p !== NO_DEPENDENCY_ID )
66+ packages .value = [... withoutNoDep , name , NO_DEPENDENCY_ID ]
67+ } else {
68+ packages .value = [... packages .value , name ]
69+ }
3670 inputValue .value = ' '
3771}
3872
@@ -63,16 +97,28 @@ function handleBlur() {
6397 :key =" pkg"
6498 class =" inline-flex items-center gap-2 px-3 py-1.5 bg-bg-subtle border border-border rounded-md"
6599 >
100+ <!-- No dependency display -->
101+ <template v-if =" pkg === NO_DEPENDENCY_ID " >
102+ <span class =" text-sm text-accent italic flex items-center gap-1.5" >
103+ <span class =" i-carbon:clean w-3.5 h-3.5" aria-hidden =" true" />
104+ {{ $t('compare.no_dependency.label') }}
105+ </span >
106+ </template >
66107 <NuxtLink
108+ v-else
67109 :to =" `/package/${pkg}`"
68110 class =" font-mono text-sm text-fg hover:text-accent transition-colors"
69111 >
70112 {{ pkg }}
71113 </NuxtLink >
72114 <button
73115 type =" button"
74- class =" text-fg-subtle hover:text-fg transition-colors focus-visible:outline-accent/70 rounded"
75- :aria-label =" $t('compare.selector.remove_package', { package: pkg })"
116+ class =" text-fg-subtle hover:text-fg transition-colors rounded"
117+ :aria-label ="
118+ $t('compare.selector.remove_package', {
119+ package: pkg === NO_DEPENDENCY_ID ? $t('compare.no_dependency.label') : pkg,
120+ })
121+ "
76122 @click =" removePackage(pkg)"
77123 >
78124 <span class =" i-carbon:close flex items-center w-3.5 h-3.5" aria-hidden =" true" />
@@ -118,17 +164,36 @@ function handleBlur() {
118164 leave-to-class =" opacity-0"
119165 >
120166 <div
121- v-if =" isInputFocused && (filteredResults.length > 0 || isSearching)"
167+ v-if ="
168+ isInputFocused && (filteredResults.length > 0 || isSearching || showNoDependencyOption)
169+ "
122170 class =" absolute top-full inset-x-0 mt-1 bg-bg-elevated border border-border rounded-lg shadow-lg z-50 max-h-64 overflow-y-auto"
123171 >
172+ <!-- No dependency option (easter egg with James) -->
173+ <button
174+ v-if =" showNoDependencyOption"
175+ type =" button"
176+ class =" w-full text-start px-4 py-2.5 hover:bg-bg-muted transition-colors focus-visible:outline-none focus-visible:bg-bg-muted border-b border-border/50"
177+ :aria-label =" $t('compare.no_dependency.add_column')"
178+ @click =" addPackage(NO_DEPENDENCY_ID)"
179+ >
180+ <div class =" text-sm text-accent italic flex items-center gap-2" >
181+ <span class =" i-carbon:clean w-4 h-4" aria-hidden =" true" />
182+ {{ $t('compare.no_dependency.typeahead_title') }}
183+ </div >
184+ <div class =" text-xs text-fg-muted truncate mt-0.5" >
185+ {{ $t('compare.no_dependency.typeahead_description') }}
186+ </div >
187+ </button >
188+
124189 <div v-if =" isSearching" class =" px-4 py-3 text-sm text-fg-muted" >
125190 {{ $t('compare.selector.searching') }}
126191 </div >
127192 <button
128193 v-for =" result in filteredResults"
129194 :key =" result.name"
130195 type =" button"
131- class =" w-full text-left px-4 py-2.5 hover:bg-bg-muted transition-colors focus-visible:outline-none focus-visible:bg-bg-muted"
196+ class =" w-full text-start px-4 py-2.5 hover:bg-bg-muted transition-colors focus-visible:outline-none focus-visible:bg-bg-muted"
132197 @click =" addPackage(result.name)"
133198 >
134199 <div class =" font-mono text-sm text-fg" >{{ result.name }}</div >
@@ -142,7 +207,12 @@ function handleBlur() {
142207
143208 <!-- Hint -->
144209 <p class =" text-xs text-fg-subtle" >
145- {{ $t('compare.selector.packages_selected', { count: packages.length, max: maxPackages }) }}
210+ {{
211+ $t('compare.selector.packages_selected', {
212+ count: packages.length,
213+ max: maxPackages,
214+ })
215+ }}
146216 <span v-if =" packages.length < 2" >{{ $t('compare.selector.add_hint') }}</span >
147217 </p >
148218 </div >
0 commit comments