@vuecs/link
Version:
A package containing a link component.
1 lines • 12 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils.ts","../src/component.ts","../src/index.ts"],"sourcesContent":["export function isObject(input: unknown) : input is Record<string, any> {\n return typeof input === 'object' &&\n input !== null &&\n !Array.isArray(input);\n}\n","import type {\n DefineComponent,\n ExtractPublicPropTypes,\n PropType,\n VNodeProps,\n VNodeTypes,\n} from 'vue';\nimport {\n computed,\n defineComponent,\n h,\n resolveDynamicComponent,\n toRef,\n} from 'vue';\nimport type { LinkQuery } from './types';\nimport { isObject } from './utils';\n\nconst linkProps = {\n active: {\n type: Boolean,\n default: false,\n },\n disabled: {\n type: Boolean,\n default: false,\n },\n href: {\n type: String,\n default: undefined,\n },\n prefetch: {\n type: Boolean,\n default: true,\n },\n target: {\n type: String,\n default: '_self',\n },\n to: {\n type: [String, Object] as PropType<string | Record<string, any>>,\n default: undefined,\n },\n query: { type: Object as PropType<LinkQuery> },\n};\n\nexport type LinkProps = ExtractPublicPropTypes<typeof linkProps>;\n\nexport const VCLink = defineComponent({\n name: 'VCLink',\n props: linkProps,\n emits: ['click', 'clicked'],\n setup(props, { emit, slots }) {\n const query = toRef(props, 'query');\n\n const routerLink = resolveDynamicComponent('RouterLink');\n const nuxtLink = resolveDynamicComponent('NuxtLink');\n\n const computedTag = computed(() => {\n const hasRouter = typeof routerLink !== 'string';\n\n if (!hasRouter || props.disabled || !props.to) {\n return 'a';\n }\n\n if (typeof nuxtLink !== 'string') {\n return 'nuxt-link';\n }\n\n return 'router-link';\n });\n\n const extendLinkWithQuery = (link: string, query: Record<string, any>) => {\n const keys = Object.keys(query);\n if (keys.length === 0) {\n return link;\n }\n\n // Split the fragment off before touching the search string. A URL's\n // fragment is always last, so appending `?…` to a link that already\n // ends in `#anchor` produces `/docs#install?ref=x` — the browser\n // reads `?ref=x` as fragment text and never sends the query. The\n // `split('?')` below would also drop the fragment outright for a\n // link carrying both. Re-attached after the query is built.\n let fragment = '';\n const fragmentIndex = link.indexOf('#');\n if (fragmentIndex !== -1) {\n fragment = link.slice(fragmentIndex);\n link = link.slice(0, fragmentIndex);\n }\n\n let searchParams : URLSearchParams;\n if (link.includes('?')) {\n const url = new URL(link, 'http://localhost:3000');\n for (const key of keys) {\n url.searchParams.set(key, query[key]);\n }\n\n searchParams = url.searchParams;\n\n [link] = link.split('?');\n } else {\n searchParams = new URLSearchParams(query);\n }\n\n return `${link}?${searchParams.toString()}${fragment}`;\n };\n\n const isRouterLink = computed(() => computedTag.value !== 'a');\n\n const computedHref = computed(() => {\n if (props.href) {\n if (\n props.query &&\n query.value\n ) {\n return extendLinkWithQuery(props.href, query.value);\n }\n\n return props.href;\n }\n\n if (isRouterLink.value) {\n return null;\n }\n\n return '#';\n });\n\n const computedProps = computed(() => {\n if (!isRouterLink.value) {\n return {};\n }\n\n let to : Record<string, any> | string | undefined;\n\n if (\n props.query &&\n query.value\n ) {\n if (typeof props.to === 'string') {\n to = extendLinkWithQuery(props.to, query.value);\n } else if (isObject(props.to)) {\n to = {\n ...props.to,\n query: {\n ...query.value,\n ...(props.to.query ? { ...props.to.query } : {}),\n },\n };\n }\n } else {\n to = props.to;\n }\n\n return {\n ...(to ? { to } : {}),\n ...(typeof props.prefetch !== 'undefined' ? { prefetch: props.prefetch } : {}),\n };\n });\n\n const computedAttrs = computed(() => ({\n // `computedHref`, NOT the raw `props.href` (#1705): the\n // query-extended href was computed and then thrown away, so\n // `:query` silently did nothing on the plain-`<a>` path — it only\n // ever reached the DOM via `computedProps.to` in router mode.\n // Keep the `props.href` guard so an hrefless `<VCLink>` still\n // renders no `href` at all; `computedHref`'s `'#'` is a\n // click-suppression sentinel and must not leak into the DOM.\n ...(props.href ? { href: computedHref.value } : {}),\n ...(isRouterLink.value ? {} : { target: props.target }),\n }));\n\n const buildVNodeProps = () => {\n const onClick = (event: Record<string, any>) => {\n const isEvent: boolean = typeof event.preventDefault !== 'undefined' &&\n typeof event.stopPropagation !== 'undefined' &&\n typeof event.stopImmediatePropagation !== 'undefined';\n\n if (isEvent && props.disabled) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n } else {\n if (isRouterLink.value) {\n emit('click', event);\n }\n\n emit('clicked', event);\n }\n\n if (\n isEvent &&\n !isRouterLink.value &&\n computedHref.value === '#'\n ) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n }\n };\n\n const vNodeProps: VNodeProps & Record<string, any> = {\n class: {\n active: props.active,\n disabled: props.disabled,\n },\n 'data-active': props.active ? '' : undefined,\n 'data-disabled': props.disabled ? '' : undefined,\n ...computedAttrs.value,\n ...computedProps.value,\n onClick,\n };\n\n return vNodeProps;\n };\n\n // Resolve the rendered component INSIDE the render fn so it\n // reacts to `to`/`disabled` changes — previously the switch\n // ran once at setup and cached the choice, so toggling\n // `:disabled` couldn't swap `<router-link>` → `<a>`.\n const resolvedComponent = computed<string | VNodeTypes>(() => {\n switch (computedTag.value) {\n case 'router-link': return routerLink;\n case 'nuxt-link': return nuxtLink;\n default: return 'a';\n }\n });\n\n return () => {\n const component = resolvedComponent.value;\n if (typeof component === 'string') {\n return h(\n component,\n buildVNodeProps(),\n [\n (typeof slots.default === 'function' ? slots.default() : []),\n ],\n );\n }\n return h(\n component as DefineComponent,\n buildVNodeProps(),\n { default: () => (typeof slots.default === 'function' ? slots.default() : []) },\n );\n };\n },\n});\n","import type { App, Plugin } from 'vue';\n\nimport './vue';\n\n// Import vue components\nimport {\n VCLink,\n} from './component';\nimport type { Options } from './types';\n\nexport * from './component';\nexport * from './types';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function install(instance: App, options?: Options) : void {\n instance.component('VCLink', VCLink);\n}\n\nexport default { install } satisfies Plugin<Options | undefined>;\n"],"mappings":"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"}