-
Notifications
You must be signed in to change notification settings - Fork 301
πͺ Location Hooks #354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
πͺ Location Hooks #354
Changes from all commits
ac0b358
dfcd5dc
6a71bd9
edcb188
27c2575
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| <script setup lang="ts" generic="T extends HookName"> | ||
| import type { Component } from 'vue'; | ||
| import type { HookName, HookContext } from '../composables/useHooks'; | ||
|
|
||
| interface Props { | ||
| /** The name of the hook outlet */ | ||
| name: T; | ||
| /** Context data to pass to hook renderers */ | ||
| ctx?: HookContext<T>; | ||
| /** Optional wrapper element or component */ | ||
| as?: string | Component; | ||
| /** Whether this outlet is required (warns in dev if no hooks registered) */ | ||
| required?: boolean; | ||
| } | ||
|
|
||
| const props = withDefaults(defineProps<Props>(), { | ||
| ctx: () => ({}) as HookContext<T>, | ||
| as: 'div', | ||
| required: false, | ||
| }); | ||
|
|
||
| const { get } = useHooks(); | ||
|
|
||
| // Warn in development if required outlet has no hooks | ||
| if (process.env.NODE_ENV !== 'production' && props.required) { | ||
| watchEffect(() => { | ||
| const allEntries = get(props.name); | ||
| if (allEntries.length === 0) { | ||
| console.warn(`[HookOutlet] Required outlet "${props.name}" has no hooks registered.`); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| // Get hook entries for this outlet | ||
| const entries = computed(() => { | ||
| const allEntries = get(props.name); | ||
|
|
||
| // Filter by when condition | ||
| return allEntries.filter((entry: any) => { | ||
| if (!entry.when) return true; | ||
| try { | ||
| return entry.when(props.ctx); | ||
| } catch (error) { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| console.error(`[HookOutlet] Error evaluating "when" condition for hook "${entry.id}" at outlet "${props.name}":`, error); | ||
| } | ||
| return false; | ||
|
Comment on lines
+43
to
+47
|
||
| } | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Render a single hook entry | ||
| */ | ||
| const renderEntry = (entry: any) => { | ||
| try { | ||
| if (typeof entry.renderer === 'function') { | ||
| return entry.renderer(props.ctx); | ||
| } | ||
| return h(entry.renderer, { ctx: props.ctx }); | ||
| } catch (error) { | ||
| if (process.env.NODE_ENV !== 'production') { | ||
| console.error(`[HookOutlet] Error rendering hook "${entry.id}" at outlet "${props.name}":`, error); | ||
| } | ||
| return null; | ||
| } | ||
|
Comment on lines
+61
to
+66
|
||
| }; | ||
| </script> | ||
|
|
||
| <template> | ||
| <template v-if="entries.length > 0"> | ||
| <component :is="as"> | ||
| <component :is="() => renderEntry(entry)" v-for="entry in entries" :key="entry.id" /> | ||
| </component> | ||
| </template> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <script setup lang="ts"> | ||
| // Example hook: Cart upsell suggestion | ||
| const props = defineProps<{ ctx: { cart: any } }>(); | ||
|
|
||
| // Example: Suggest free shipping threshold | ||
| const freeShippingThreshold = 50; | ||
| const subtotal = computed(() => { | ||
| if (!props.ctx?.cart) return 0; | ||
| const totalString = props.ctx.cart.subtotal?.replace(/[^0-9.]/g, '') || '0'; | ||
| return parseFloat(totalString); | ||
| }); | ||
|
|
||
| const runtimeConfig = useRuntimeConfig(); | ||
| const currencySymbol = runtimeConfig?.public?.CURRENCY_SYMBOL || '$'; | ||
|
|
||
| const remaining = computed(() => Math.max(0, freeShippingThreshold - subtotal.value)); | ||
| const hasReachedFreeShipping = computed(() => remaining.value === 0); | ||
| </script> | ||
|
|
||
| <template> | ||
| <div v-if="!hasReachedFreeShipping" class="p-3 text-sm text-center items-center text-gray-700 bg-gray-100 rounded-lg dark:text-blue-300 leading-loose"> | ||
| Add <strong>{{ currencySymbol }}{{ remaining.toFixed(2) }}</strong> more for <strong>FREE shipping</strong> | ||
| </div> | ||
| </template> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'required' prop is defined in the Props interface with documentation stating it "warns in dev if no hooks registered", but this prop is never actually used in the component logic. Either implement the warning functionality or remove this unused prop to avoid confusion.