import type { IconifyIcon, IconifyJSON, PartialIconifyAPIConfig } from '@iconify/vue' import type { App, Component, Directive, Plugin, Ref } from 'vue' import { addAPIProvider, addCollection, addIcon, } from '@iconify/vue' import { kebabCase } from 'change-case' import { DEFAULT_ICONIFY_PROVIDER, INJECTION_KEY_VOLVER } from './constants' const DEFAULT_FETCH_ICON_OPTIONS: RequestInit = { cache: 'force-cache' } export function useDefaultProps( component: Component, defaults?: Record, name?: string, ) { const componentName = name || component.name if (!componentName) { return component } const componentDefaults = defaults?.[componentName] as Record< string, unknown > const props = (component as Record).props as Record< string, unknown > if (!componentDefaults || !props) { return { ...component, name: componentName } } return { ...component, name: componentName, props: Object.keys(props).reduce( (acc, key) => { if (!(key in componentDefaults)) { acc[key] = props[key] return acc } const customDefault = componentDefaults[key] if ( typeof props[key] === 'function' || Array.isArray(props[key]) ) { acc[key] = { type: props[key], default: customDefault, } return acc } acc[key] = { ...(props[key] as Record), default: customDefault, } return acc }, {} as Record, ), } } export type DefaultOptions = Record> export type VolverExperimentalFeatures = { /** * Force suggestions in `VvInputText` and `VvTextarea` component */ forceInputSuggestions?: boolean } export type VolverOptions = { /** * If true set "fetchOptions" with credentials: 'include' */ fetchWithCredentials?: boolean /** * Optional fetch params */ fetchOptions?: RequestInit /** * Array of iconify collections that will be added during plugin install * @see https://docs.iconify.design/types/iconify-json.html */ iconsCollections?: IconifyJSON[] /** * Set true inside nuxt */ nuxt?: boolean /** * Default iconify provider * @see https://docs.iconify.design/icon-components/vue/add-collection.html * @default 'vv' */ iconsProvider?: string /** * Components to install */ components?: Record /** * Alias to install */ aliases?: Record /** * Directives to install */ directives?: Record /** * Default props for components */ defaults?: DefaultOptions /** * Experimental features */ experimentalFeatures?: VolverExperimentalFeatures } export interface VolverInterface { /** * @param {string} src Icon source path (url) * @param {RequestInit} options * @returns {Promise} String SVG if exist */ fetchIcon: (src: string, options?: RequestInit) => Promise /** * Add iconify collection to library * @see https://docs.iconify.design/icon-components/vue/add-collection.html * @param {IconifyJSON} collection * @param {string} providerName Optional provider name */ addCollection: (collection: IconifyJSON, providerName?: string) => boolean /** * Add icon to collection * @see https://docs.iconify.design/icon-components/vue/add-icon.html * @param {string} name * @param {IconifyIcon} data * @returns {boolean} true on success, false if something is wrong with data */ addIcon: (name: string, data: IconifyIcon) => boolean /** * Add custom config for icons provider * @param {string} provider * @param {PartialIconifyAPIConfig} customConfig * @returns {boolean} true on success, false if something is wrong with data */ addIconsAPIProvider: ( provider: string, customConfig: PartialIconifyAPIConfig, ) => boolean /** * Current provider */ iconsProvider: string /** * Array of installed iconify collections * @see https://docs.iconify.design/types/iconify-json.html */ iconsCollections: IconifyJSON[] /** * Set true inside nuxt */ nuxt: boolean /** * Components defaults options */ defaults: Ref } export class Volver implements VolverInterface { private readonly _fetchOptions: RequestInit = {} private readonly _iconsCollections: IconifyJSON[] = [] private readonly _iconsProvider: string = DEFAULT_ICONIFY_PROVIDER private readonly _nuxt: boolean = false private readonly _experimentalFeatures: VolverExperimentalFeatures = {} defaults: Ref = ref({}) constructor({ fetchWithCredentials, fetchOptions, iconsProvider, nuxt, iconsCollections, defaults, experimentalFeatures, }: VolverOptions = {}) { // fetch options if (fetchOptions) { this._fetchOptions = fetchOptions } // fetch with credentials sugar syntax if (fetchWithCredentials) { this._fetchOptions = { ...this._fetchOptions, credentials: 'include', } } // default iconify provider if (iconsProvider) { this._iconsProvider = iconsProvider } // enable nuxt mode if (nuxt) { this._nuxt = nuxt } // add iconify collections if (iconsCollections && Array.isArray(iconsCollections)) { iconsCollections.forEach((iconsCollection) => { this.addCollection(iconsCollection, this._iconsProvider) }) } // set defaults if (defaults) { this.defaults.value = defaults } // experimental features if (experimentalFeatures) { this._experimentalFeatures = experimentalFeatures } } get nuxt() { return this._nuxt } get iconsProvider() { return this._iconsProvider } get iconsCollections() { return this._iconsCollections } get experimentalFeatures() { return this._experimentalFeatures } addCollection( collection: IconifyJSON, providerName: string = this._iconsProvider, ): boolean { this._iconsCollections.push(collection) return addCollection(collection, providerName) } addIcon(name: string, data: IconifyIcon): boolean { return addIcon(name, data) } addIconsAPIProvider( provider: string, customConfig: PartialIconifyAPIConfig, ): boolean { return addAPIProvider(provider, customConfig) } async fetchIcon( src: string, options: RequestInit = DEFAULT_FETCH_ICON_OPTIONS, ): Promise { const response = await fetch(src, { ...this._fetchOptions, ...options, }) if (!response.ok) { throw new Error( `Cannot fetch icon "${src}": ${response.status} ${response.statusText}`, ) } return response.text() } } const VolverPlugin: Plugin = { /** * Vue.use() hook * @param {App} app * @param {object} options */ install(app: App, options: VolverOptions = {}) { const volver = new Volver(options) // register global methods app.config.globalProperties.$vv = volver // register components if (options.components) { Object.entries(options.components).forEach(([name, component]) => { app.component( name, useDefaultProps(component, options.defaults), ) }) } // register aliases if (options.aliases) { Object.entries(options.aliases).forEach(([name, component]) => { app.component( name, useDefaultProps(component, options.defaults, name), ) }) } // register directives if (options.directives) { Object.entries(options.directives).forEach(([name, directive]) => { const kebabName = kebabCase(name) if (kebabName.startsWith('v-')) { name = kebabName.substring(2).toLocaleLowerCase() } app.directive(name, directive) }) } // provide volver to components app.provide(INJECTION_KEY_VOLVER, volver) }, } export default VolverPlugin