| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | <template> | 
					
						
							|  |  |  |   <div> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |     <slot v-bind="{ open, close }" /> | 
					
						
							|  |  |  |     <v-dialog | 
					
						
							|  |  |  |       v-model="dialog" | 
					
						
							|  |  |  |       max-width="988px" | 
					
						
							|  |  |  |       content-class="top-dialog" | 
					
						
							|  |  |  |       :scrollable="false" | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       <v-app-bar | 
					
						
							|  |  |  |         sticky | 
					
						
							|  |  |  |         dark | 
					
						
							|  |  |  |         color="primary-lighten-1 top-0 position-relative left-0" | 
					
						
							|  |  |  |         :rounded="!$vuetify.display.xs" | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |         <v-text-field | 
					
						
							|  |  |  |           id="arrow-search" | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |           v-model="search.query.value" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           autofocus | 
					
						
							| 
									
										
										
										
											2025-06-28 15:59:58 +02:00
										 |  |  |           variant="solo" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           flat | 
					
						
							|  |  |  |           autocomplete="off" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           bg-color="primary-lighten-1" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           color="white" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           density="compact" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           class="mx-2 arrow-search" | 
					
						
							|  |  |  |           hide-details | 
					
						
							|  |  |  |           single-line | 
					
						
							| 
									
										
										
										
											2022-08-10 07:12:45 +02:00
										 |  |  |           :placeholder="$t('search.search')" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           :prepend-inner-icon="$globals.icons.search" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         /> | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |         <v-btn | 
					
						
							|  |  |  |           v-if="$vuetify.display.xs" | 
					
						
							|  |  |  |           size="x-small" | 
					
						
							|  |  |  |           class="rounded-circle" | 
					
						
							|  |  |  |           light | 
					
						
							|  |  |  |           @click="dialog = false" | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           <v-icon> | 
					
						
							|  |  |  |             {{ $globals.icons.close }} | 
					
						
							|  |  |  |           </v-icon> | 
					
						
							|  |  |  |         </v-btn> | 
					
						
							|  |  |  |       </v-app-bar> | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |       <v-card | 
					
						
							|  |  |  |         class="position-relative mt-1 pa-1 scroll" | 
					
						
							|  |  |  |         max-height="700px" | 
					
						
							|  |  |  |         relative | 
					
						
							|  |  |  |         :loading="loading" | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |         <v-card-actions> | 
					
						
							|  |  |  |           <div class="mr-auto"> | 
					
						
							|  |  |  |             {{ $t("search.results") }} | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </v-card-actions> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         <RecipeCardMobile | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  |           v-for="(recipe, index) in search.data.value" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           :key="index" | 
					
						
							|  |  |  |           :tabindex="index" | 
					
						
							|  |  |  |           class="ma-1 arrow-nav" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           :name="recipe.name ?? ''" | 
					
						
							|  |  |  |           :description="recipe.description ?? ''" | 
					
						
							|  |  |  |           :slug="recipe.slug ?? ''" | 
					
						
							|  |  |  |           :rating="recipe.rating ?? 0" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |           :image="recipe.image" | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  |           :recipe-id="recipe.id ?? ''" | 
					
						
							|  |  |  |           v-bind="$attrs.selected ? { selected: () => handleSelect(recipe) } : {}" | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |         /> | 
					
						
							|  |  |  |       </v-card> | 
					
						
							|  |  |  |     </v-dialog> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | </template> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | <script setup lang="ts"> | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | import RecipeCardMobile from "./RecipeCardMobile.vue"; | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  | import { useLoggedInState } from "~/composables/use-logged-in-state"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | import type { RecipeSummary } from "~/lib/api/types/recipe"; | 
					
						
							| 
									
										
										
										
											2023-02-11 21:26:10 -09:00
										 |  |  | import { useUserApi } from "~/composables/api"; | 
					
						
							| 
									
										
										
										
											2023-03-12 12:59:28 -08:00
										 |  |  | import { useRecipeSearch } from "~/composables/recipes/use-recipe-search"; | 
					
						
							| 
									
										
										
										
											2023-11-05 19:07:02 -06:00
										 |  |  | import { usePublicExploreApi } from "~/composables/api/api-client"; | 
					
						
							| 
									
										
										
										
											2025-06-20 00:09:12 +07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | const SELECTED_EVENT = "selected"; | 
					
						
							| 
									
										
										
										
											2022-01-09 07:15:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | // Define emits
 | 
					
						
							|  |  |  | const emit = defineEmits<{ | 
					
						
							|  |  |  |   selected: [recipe: RecipeSummary]; | 
					
						
							|  |  |  | }>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const $auth = useMealieAuth(); | 
					
						
							|  |  |  | const loading = ref(false); | 
					
						
							|  |  |  | const selectedIndex = ref(-1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ===========================================================================
 | 
					
						
							|  |  |  | // Dialog State Management
 | 
					
						
							|  |  |  | const dialog = ref(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Reset or Grab Recipes on Change
 | 
					
						
							|  |  |  | watch(dialog, (val) => { | 
					
						
							|  |  |  |   if (!val) { | 
					
						
							|  |  |  |     search.query.value = ""; | 
					
						
							|  |  |  |     selectedIndex.value = -1; | 
					
						
							|  |  |  |     search.data.value = []; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ===========================================================================
 | 
					
						
							|  |  |  | // Event Handlers
 | 
					
						
							| 
									
										
										
										
											2022-01-09 07:15:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | function selectRecipe() { | 
					
						
							|  |  |  |   const recipeCards = document.getElementsByClassName("arrow-nav"); | 
					
						
							|  |  |  |   if (recipeCards) { | 
					
						
							|  |  |  |     if (selectedIndex.value < 0) { | 
					
						
							|  |  |  |       selectedIndex.value = -1; | 
					
						
							|  |  |  |       document.getElementById("arrow-search")?.focus(); | 
					
						
							|  |  |  |       return; | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (selectedIndex.value >= recipeCards.length) { | 
					
						
							|  |  |  |       selectedIndex.value = recipeCards.length - 1; | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  |     (recipeCards[selectedIndex.value] as HTMLElement).focus(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-02-20 02:11:52 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | function onUpDown(e: KeyboardEvent) { | 
					
						
							|  |  |  |   if (e.key === "Enter") { | 
					
						
							|  |  |  |     console.log(document.activeElement); | 
					
						
							|  |  |  |     // (document.activeElement as HTMLElement).click();
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (e.key === "ArrowUp") { | 
					
						
							|  |  |  |     e.preventDefault(); | 
					
						
							|  |  |  |     selectedIndex.value--; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else if (e.key === "ArrowDown") { | 
					
						
							|  |  |  |     e.preventDefault(); | 
					
						
							|  |  |  |     selectedIndex.value++; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   selectRecipe(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | watch(dialog, (val) => { | 
					
						
							|  |  |  |   if (!val) { | 
					
						
							|  |  |  |     document.removeEventListener("keyup", onUpDown); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else { | 
					
						
							|  |  |  |     document.addEventListener("keyup", onUpDown); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const route = useRoute(); | 
					
						
							|  |  |  | const groupSlug = computed(() => route.params.groupSlug as string || $auth.user.value?.groupSlug || ""); | 
					
						
							|  |  |  | watch(route, close); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function open() { | 
					
						
							|  |  |  |   dialog.value = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | function close() { | 
					
						
							|  |  |  |   dialog.value = false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ===========================================================================
 | 
					
						
							|  |  |  | // Basic Search
 | 
					
						
							|  |  |  | const { isOwnGroup } = useLoggedInState(); | 
					
						
							|  |  |  | const api = isOwnGroup.value ? useUserApi() : usePublicExploreApi(groupSlug.value).explore; | 
					
						
							|  |  |  | const search = useRecipeSearch(api); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Select Handler
 | 
					
						
							|  |  |  | function handleSelect(recipe: RecipeSummary) { | 
					
						
							|  |  |  |   close(); | 
					
						
							|  |  |  |   emit(SELECTED_EVENT, recipe); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 20:37:02 +02:00
										 |  |  | // Expose functions to parent components
 | 
					
						
							|  |  |  | defineExpose({ | 
					
						
							|  |  |  |   open, | 
					
						
							|  |  |  |   close, | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | }); | 
					
						
							|  |  |  | </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <style> | 
					
						
							|  |  |  | .scroll { | 
					
						
							| 
									
										
										
										
											2023-02-21 00:51:24 -06:00
										 |  |  |   overflow-y: auto; | 
					
						
							| 
									
										
										
										
											2021-11-08 21:12:13 -09:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-09 07:15:23 +01:00
										 |  |  | </style> |