diff --git a/.github/actions/pnpm-install/action.yml b/.github/actions/pnpm-install/action.yml index ac8aa599b79..fa18384a880 100644 --- a/.github/actions/pnpm-install/action.yml +++ b/.github/actions/pnpm-install/action.yml @@ -13,6 +13,9 @@ runs: /home/runner/.cache/Cypress /home/runner/.pnpm-store key: pnpm-${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }} + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc - uses: pnpm/action-setup@v4 - run: pnpm i --frozen-lockfile shell: bash diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..dc5f6a52b13 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.6.0 diff --git a/package.json b/package.json index 52a8aa0aac4..c4b45c44aa2 100755 --- a/package.json +++ b/package.json @@ -27,19 +27,19 @@ "vue-ecosystem-ci:test": "pnpm --filter vuetify run lint && pnpm --filter vuetify run test" }, "engines": { - "node": ">=18.20.0 || >=20.10.0" + "node": ">=22.0.0" }, "devDependencies": { "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.24.7", - "@mdi/font": "6.2.95", - "@mdi/js": "6.2.95", - "@mdi/svg": "6.2.95", + "@mdi/font": "7.4.47", + "@mdi/js": "7.4.47", + "@mdi/svg": "7.4.47", "@octokit/core": "^5.2.0", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@unhead/vue": "^1.9.4", "@vue/compiler-sfc": "^3.4.27", "@vue/runtime-core": "^3.4.27", @@ -79,15 +79,20 @@ "semver": "^7.6.0", "shelljs": "^0.8.5", "stringify-object": "^5.0.0", - "typescript": "~5.3.3", + "typescript": "~5.5.4", "upath": "^2.0.1", "vite-plugin-inspect": "^0.8.3", "vite-plugin-warmup": "^0.1.0", "vue": "^3.4.27", "vue-analytics": "^5.16.1", "vue-router": "^4.3.0", - "vue-tsc": "^1.8.27", + "vue-tsc": "^2.0.29", "yargs": "^17.7.2" }, - "packageManager": "pnpm@9.6.0" + "packageManager": "pnpm@9.6.0", + "pnpm": { + "patchedDependencies": { + "@mdi/js@7.4.47": "patches/@mdi__js@7.4.47.patch" + } + } } diff --git a/packages/api-generator/package.json b/packages/api-generator/package.json index 3ae4d481272..97a2dba0bf0 100755 --- a/packages/api-generator/package.json +++ b/packages/api-generator/package.json @@ -20,6 +20,7 @@ "vuetify": "workspace:*" }, "devDependencies": { + "@types/lodash-es": "^4.17.12", "@types/stringify-object": "^4.0.5", "eslint": "^8.57.0", "lodash-es": "^4.17.21", diff --git a/packages/docs/auto-imports.d.ts b/packages/docs/auto-imports.d.ts index 15cb2c8530e..b41ec364536 100644 --- a/packages/docs/auto-imports.d.ts +++ b/packages/docs/auto-imports.d.ts @@ -6,11 +6,13 @@ export {} declare global { const ComponentPublicInstance: typeof import('vue')['ComponentPublicInstance'] + const EffectScope: typeof import('vue')['EffectScope'] const IN_BROWSER: typeof import('./src/utils/globals')['IN_BROWSER'] const IS_DEBUG: typeof import('./src/utils/globals')['IS_DEBUG'] const IS_PROD: typeof import('./src/utils/globals')['IS_PROD'] const IS_SERVER: typeof import('./src/utils/globals')['IS_SERVER'] const PropType: typeof import('vue')['PropType'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] const anyLanguagePattern: typeof import('./src/utils/routes')['anyLanguagePattern'] const camelCase: typeof import('lodash-es')['camelCase'] const camelize: typeof import('vue')['camelize'] @@ -18,43 +20,85 @@ declare global { const configureMarkdown: typeof import('./src/utils/markdown-it')['configureMarkdown'] const copyElementContent: typeof import('./src/utils/helpers')['copyElementContent'] const createAdProps: typeof import('./src/composables/ad')['createAdProps'] + const createApp: typeof import('vue')['createApp'] const createOne: typeof import('@vuetify/one')['createOne'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] const defineStore: typeof import('pinia')['defineStore'] const disabledLanguagePattern: typeof import('./src/utils/routes')['disabledLanguagePattern'] - const distance: typeof import('./src/utils/helpers')['distance'] + const effectScope: typeof import('vue')['effectScope'] const eventName: typeof import('./src/utils/helpers')['eventName'] const genAppMetaInfo: typeof import('./src/utils/metadata')['genAppMetaInfo'] const genMetaInfo: typeof import('./src/utils/metadata')['genMetaInfo'] const generatedRoutes: typeof import('./src/utils/routes')['generatedRoutes'] + const getActivePinia: typeof import('pinia')['getActivePinia'] const getBranch: typeof import('./src/utils/helpers')['getBranch'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const getDistance: typeof import('./src/utils/helpers')['getDistance'] const getMatchMedia: typeof import('./src/utils/helpers')['getMatchMedia'] const gtagClick: typeof import('./src/utils/analytics')['gtagClick'] const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] const insertLinks: typeof import('./src/utils/api')['insertLinks'] const isOn: typeof import('./src/utils/helpers')['isOn'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] const kebabCase: typeof import('lodash-es')['kebabCase'] const languagePattern: typeof import('./src/utils/routes')['languagePattern'] const leadingSlash: typeof import('./src/utils/routes')['leadingSlash'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] const markdownItRules: typeof import('./src/utils/markdown-it-rules')['default'] const mergeProps: typeof import('vue')['mergeProps'] const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeUnMount: typeof import('vue')['onBeforeUnMount'] const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onScopeDispose: typeof import('vue')['onScopeDispose'] const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] const preferredLocale: typeof import('./src/utils/routes')['preferredLocale'] const propsToString: typeof import('./src/utils/helpers')['propsToString'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] const redirectRoutes: typeof import('./src/utils/routes')['redirectRoutes'] const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] const rpath: typeof import('./src/utils/routes')['rpath'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import('vue')['shallowRef'] const storeToRefs: typeof import('pinia')['storeToRefs'] const stripLinks: typeof import('./src/utils/api')['stripLinks'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] const trailingSlash: typeof import('./src/utils/routes')['trailingSlash'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] const upperFirst: typeof import('lodash-es')['upperFirst'] const useAd: typeof import('./src/composables/ad')['useAd'] const useAdsStore: typeof import('./src/stores/ads')['useAdsStore'] @@ -63,13 +107,17 @@ declare global { const useAuthStore: typeof import('@vuetify/one')['useAuthStore'] const useCommitsStore: typeof import('./src/stores/commits')['useCommitsStore'] const useCosmic: typeof import('./src/composables/cosmic')['useCosmic'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] const useDate: typeof import('vuetify')['useDate'] const useDisplay: typeof import('vuetify')['useDisplay'] + const useFrontmatter: typeof import('./src/composables/frontmatter')['useFrontmatter'] const useGoTo: typeof import('vuetify')['useGoTo'] const useGtag: typeof import('vue-gtag-next')['useGtag'] const useHttpStore: typeof import('@vuetify/one')['useHttpStore'] const useI18n: typeof import('vue-i18n')['useI18n'] const useJobsStore: typeof import('./src/stores/jobs')['useJobsStore'] + const useLink: typeof import('vue-router')['useLink'] const useLocaleStore: typeof import('./src/stores/locale')['useLocaleStore'] const useMadeWithVuetifyStore: typeof import('./src/stores/made-with-vuetify')['useMadeWithVuetifyStore'] const useOneStore: typeof import('@vuetify/one')['useOneStore'] @@ -84,6 +132,7 @@ declare global { const useRtl: typeof import('vuetify')['useRtl'] const useSettingsStore: typeof import('@vuetify/one')['useSettingsStore'] const useShopifyStore: typeof import('./src/stores/shopify')['useShopifyStore'] + const useSlots: typeof import('vue')['useSlots'] const useSponsorsStore: typeof import('./src/stores/sponsors')['useSponsorsStore'] const useSpotStore: typeof import('./src/stores/spot')['useSpotStore'] const useTeamStore: typeof import('./src/stores/team')['useTeamStore'] @@ -93,17 +142,27 @@ declare global { const waitForReadystate: typeof import('./src/utils/helpers')['waitForReadystate'] const watch: typeof import('vue')['watch'] const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] const wrapInArray: typeof import('./src/utils/helpers')['wrapInArray'] } +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') +} // for vue template auto import import { UnwrapRef } from 'vue' declare module 'vue' { interface GlobalComponents {} interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef readonly IN_BROWSER: UnwrapRef readonly IS_DEBUG: UnwrapRef readonly IS_PROD: UnwrapRef readonly IS_SERVER: UnwrapRef + readonly acceptHMRUpdate: UnwrapRef readonly anyLanguagePattern: UnwrapRef readonly camelCase: UnwrapRef readonly camelize: UnwrapRef @@ -111,42 +170,84 @@ declare module 'vue' { readonly configureMarkdown: UnwrapRef readonly copyElementContent: UnwrapRef readonly createAdProps: UnwrapRef + readonly createApp: UnwrapRef readonly createOne: UnwrapRef + readonly createPinia: UnwrapRef + readonly customRef: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef readonly defineStore: UnwrapRef readonly disabledLanguagePattern: UnwrapRef - readonly distance: UnwrapRef + readonly effectScope: UnwrapRef readonly eventName: UnwrapRef readonly genAppMetaInfo: UnwrapRef readonly genMetaInfo: UnwrapRef readonly generatedRoutes: UnwrapRef + readonly getActivePinia: UnwrapRef readonly getBranch: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly getDistance: UnwrapRef readonly getMatchMedia: UnwrapRef readonly gtagClick: UnwrapRef readonly h: UnwrapRef + readonly inject: UnwrapRef readonly insertLinks: UnwrapRef readonly isOn: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef readonly kebabCase: UnwrapRef readonly languagePattern: UnwrapRef readonly leadingSlash: UnwrapRef + readonly mapActions: UnwrapRef + readonly mapGetters: UnwrapRef + readonly mapState: UnwrapRef + readonly mapStores: UnwrapRef + readonly mapWritableState: UnwrapRef + readonly markRaw: UnwrapRef readonly markdownItRules: UnwrapRef readonly mergeProps: UnwrapRef readonly nextTick: UnwrapRef + readonly onActivated: UnwrapRef readonly onBeforeMount: UnwrapRef readonly onBeforeRouteLeave: UnwrapRef readonly onBeforeRouteUpdate: UnwrapRef readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onErrorCaptured: UnwrapRef readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef readonly onScopeDispose: UnwrapRef readonly onServerPrefetch: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef readonly preferredLocale: UnwrapRef readonly propsToString: UnwrapRef + readonly provide: UnwrapRef + readonly reactive: UnwrapRef + readonly readonly: UnwrapRef readonly redirectRoutes: UnwrapRef readonly ref: UnwrapRef + readonly resolveComponent: UnwrapRef readonly rpath: UnwrapRef + readonly setActivePinia: UnwrapRef + readonly setMapStoreSuffix: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef readonly shallowRef: UnwrapRef readonly storeToRefs: UnwrapRef readonly stripLinks: UnwrapRef + readonly toRaw: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef readonly trailingSlash: UnwrapRef + readonly triggerRef: UnwrapRef + readonly unref: UnwrapRef readonly upperFirst: UnwrapRef readonly useAd: UnwrapRef readonly useAdsStore: UnwrapRef @@ -155,13 +256,17 @@ declare module 'vue' { readonly useAuthStore: UnwrapRef readonly useCommitsStore: UnwrapRef readonly useCosmic: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVars: UnwrapRef readonly useDate: UnwrapRef readonly useDisplay: UnwrapRef + readonly useFrontmatter: UnwrapRef readonly useGoTo: UnwrapRef readonly useGtag: UnwrapRef readonly useHttpStore: UnwrapRef readonly useI18n: UnwrapRef readonly useJobsStore: UnwrapRef + readonly useLink: UnwrapRef readonly useLocaleStore: UnwrapRef readonly useMadeWithVuetifyStore: UnwrapRef readonly useOneStore: UnwrapRef @@ -176,6 +281,7 @@ declare module 'vue' { readonly useRtl: UnwrapRef readonly useSettingsStore: UnwrapRef readonly useShopifyStore: UnwrapRef + readonly useSlots: UnwrapRef readonly useSponsorsStore: UnwrapRef readonly useSpotStore: UnwrapRef readonly useTeamStore: UnwrapRef @@ -185,16 +291,20 @@ declare module 'vue' { readonly waitForReadystate: UnwrapRef readonly watch: UnwrapRef readonly watchEffect: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef readonly wrapInArray: UnwrapRef } } declare module '@vue/runtime-core' { interface GlobalComponents {} interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef readonly IN_BROWSER: UnwrapRef readonly IS_DEBUG: UnwrapRef readonly IS_PROD: UnwrapRef readonly IS_SERVER: UnwrapRef + readonly acceptHMRUpdate: UnwrapRef readonly anyLanguagePattern: UnwrapRef readonly camelCase: UnwrapRef readonly camelize: UnwrapRef @@ -202,42 +312,84 @@ declare module '@vue/runtime-core' { readonly configureMarkdown: UnwrapRef readonly copyElementContent: UnwrapRef readonly createAdProps: UnwrapRef + readonly createApp: UnwrapRef readonly createOne: UnwrapRef + readonly createPinia: UnwrapRef + readonly customRef: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef readonly defineStore: UnwrapRef readonly disabledLanguagePattern: UnwrapRef - readonly distance: UnwrapRef + readonly effectScope: UnwrapRef readonly eventName: UnwrapRef readonly genAppMetaInfo: UnwrapRef readonly genMetaInfo: UnwrapRef readonly generatedRoutes: UnwrapRef + readonly getActivePinia: UnwrapRef readonly getBranch: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly getDistance: UnwrapRef readonly getMatchMedia: UnwrapRef readonly gtagClick: UnwrapRef readonly h: UnwrapRef + readonly inject: UnwrapRef readonly insertLinks: UnwrapRef readonly isOn: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef readonly kebabCase: UnwrapRef readonly languagePattern: UnwrapRef readonly leadingSlash: UnwrapRef + readonly mapActions: UnwrapRef + readonly mapGetters: UnwrapRef + readonly mapState: UnwrapRef + readonly mapStores: UnwrapRef + readonly mapWritableState: UnwrapRef + readonly markRaw: UnwrapRef readonly markdownItRules: UnwrapRef readonly mergeProps: UnwrapRef readonly nextTick: UnwrapRef + readonly onActivated: UnwrapRef readonly onBeforeMount: UnwrapRef readonly onBeforeRouteLeave: UnwrapRef readonly onBeforeRouteUpdate: UnwrapRef readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onErrorCaptured: UnwrapRef readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef readonly onScopeDispose: UnwrapRef readonly onServerPrefetch: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef readonly preferredLocale: UnwrapRef readonly propsToString: UnwrapRef + readonly provide: UnwrapRef + readonly reactive: UnwrapRef + readonly readonly: UnwrapRef readonly redirectRoutes: UnwrapRef readonly ref: UnwrapRef + readonly resolveComponent: UnwrapRef readonly rpath: UnwrapRef + readonly setActivePinia: UnwrapRef + readonly setMapStoreSuffix: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef readonly shallowRef: UnwrapRef readonly storeToRefs: UnwrapRef readonly stripLinks: UnwrapRef + readonly toRaw: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef readonly trailingSlash: UnwrapRef + readonly triggerRef: UnwrapRef + readonly unref: UnwrapRef readonly upperFirst: UnwrapRef readonly useAd: UnwrapRef readonly useAdsStore: UnwrapRef @@ -246,13 +398,17 @@ declare module '@vue/runtime-core' { readonly useAuthStore: UnwrapRef readonly useCommitsStore: UnwrapRef readonly useCosmic: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVars: UnwrapRef readonly useDate: UnwrapRef readonly useDisplay: UnwrapRef + readonly useFrontmatter: UnwrapRef readonly useGoTo: UnwrapRef readonly useGtag: UnwrapRef readonly useHttpStore: UnwrapRef readonly useI18n: UnwrapRef readonly useJobsStore: UnwrapRef + readonly useLink: UnwrapRef readonly useLocaleStore: UnwrapRef readonly useMadeWithVuetifyStore: UnwrapRef readonly useOneStore: UnwrapRef @@ -267,6 +423,7 @@ declare module '@vue/runtime-core' { readonly useRtl: UnwrapRef readonly useSettingsStore: UnwrapRef readonly useShopifyStore: UnwrapRef + readonly useSlots: UnwrapRef readonly useSponsorsStore: UnwrapRef readonly useSpotStore: UnwrapRef readonly useTeamStore: UnwrapRef @@ -276,6 +433,8 @@ declare module '@vue/runtime-core' { readonly waitForReadystate: UnwrapRef readonly watch: UnwrapRef readonly watchEffect: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef readonly wrapInArray: UnwrapRef } } diff --git a/packages/docs/build/api-plugin.ts b/packages/docs/build/api-plugin.ts index 60433356134..536ec1d50de 100644 --- a/packages/docs/build/api-plugin.ts +++ b/packages/docs/build/api-plugin.ts @@ -1,163 +1,28 @@ // Imports -import fs from 'fs' -import path, { resolve } from 'path' -import { createRequire } from 'module' -import { startCase } from 'lodash-es' -import locales from '../src/i18n/locales.json' -import pageToApi from '../src/data/page-to-api.json' +import fs from 'node:fs/promises' +import path from 'node:path' import type { Plugin } from 'vite' -import { rimraf } from 'rimraf' -import { mkdirp } from 'mkdirp' -const API_ROOT = resolve('../api-generator/dist/api') -const API_PAGES_ROOT = resolve('./node_modules/.cache/api-pages') - -const require = createRequire(import.meta.url) - -const sections = ['props', 'events', 'slots', 'exposed', 'sass', 'argument', 'modifiers', 'value'] as const -// This can't be imported from the api-generator because it mixes the type definitions up -type Data = { - displayName: string // user visible name used in page titles - fileName: string // file name for translation strings and generated types - pathName: string // kebab-case name for use in urls -} & Record> - -const localeList = locales - .filter(item => item.enabled) - .map(item => item.alternate || item.locale) - -function genApiLinks (componentName: string, header: string) { - const section = ['', ''] - const links = (Object.keys(pageToApi) as (keyof typeof pageToApi)[]) - .filter(page => pageToApi[page].includes(componentName)) - .reduce((acc, href) => { - const name = href.split('/')[1] - acc.push(`- [${startCase(name)}](/${href})`) - return acc - }, []) - - if (links.length && header) { - section.unshift(...[links.join('\n'), `## ${header} {#links}`]) - } - - return `${section.join('\n\n')}\n\n` -} - -function genFrontMatter (component: string) { - const fm = [ - `title: ${component} API`, - `description: API for the ${component} component.`, - `keywords: ${component}, api, vuetify`, - ] - - return `---\nmeta:\n${fm.map(s => ' ' + s).join('\n')}\n---` -} - -function genHeader (componentName: string) { - const header = [ - genFrontMatter(componentName), - `# ${componentName} API`, - '', - ] - - return `${header.join('\n\n')}\n\n` -} - -const sanitize = (str: string) => str.replace(/\$/g, '') - -async function loadMessages (locale: string) { - const prefix = path.resolve('./src/i18n/messages/') - const fallback = require(path.join(prefix, 'en.json')) - - try { - const messages = require(path.join(prefix, `${locale}.json`)) - - return { - ...fallback['api-headers'], - ...(messages['api-headers'] || {}), - } - } catch (err) { - return fallback['api-headers'] - } -} - -async function createMdFile (component: Data, locale: string) { - const messages = await loadMessages(locale) - let str = '' - - str += genHeader(component.displayName) - str += genApiLinks(component.displayName, messages.links) - - for (const section of sections) { - if (Object.keys(component[section] ?? {}).length) { - str += `## ${messages[section]} {#${section}}\n\n` - str += `\n\n` - } - } - - return str -} - -async function writeFile (componentApi: Data, locale: string) { - if (!componentApi?.fileName) return - - const folder = resolve(API_PAGES_ROOT, locale, 'api') - - if (!fs.existsSync(folder)) { - fs.mkdirSync(folder, { recursive: true }) - } - - fs.writeFileSync(resolve(folder, `${sanitize(componentApi.pathName)}.md`), await createMdFile(componentApi, locale)) -} - -function getApiData () { - const files = fs.readdirSync(API_ROOT) - const data: Data[] = [] - - for (const file of files) { - const obj = JSON.parse(fs.readFileSync(resolve(API_ROOT, file), 'utf-8')) - - data.push(obj) - } - - return data -} - -async function generateFiles () { - // const api: Record[] = getCompleteApi(localeList) - const api = getApiData() - - for (const locale of localeList) { - // const pages = {} as Record - - for (const item of api) { - await writeFile(item, locale) - - // pages[`/${locale}/api/${sanitize(kebabCase(item.name))}/`] = item.name - } - - // fs.writeFileSync(resolve(API_PAGES_ROOT, `${locale}/pages.json`), JSON.stringify(pages, null, 2)) - fs.writeFileSync(resolve(API_PAGES_ROOT, `${locale}.js`), `export default require.context('./${locale}/api', true, /\\.md$/)`) - } - - // for (const item of api) { - // writeData(item.name, item) - // } - - // fs.writeFileSync(resolve(API_PAGES_ROOT, 'sass.json'), JSON.stringify([ - // ...api.filter(item => item && item.sass && item.sass.length > 0).map(item => item.name), - // ])) -} +const API_ROOT = path.resolve('../api-generator/dist/api') export default function Api (): Plugin { return { name: 'vuetify:api', enforce: 'pre', - async config () { - await rimraf(API_PAGES_ROOT) - await mkdirp(API_PAGES_ROOT) + resolveId (id) { + return id === 'virtual:api-list' ? '\0' + id : undefined + }, + async load (id) { + if (id === '\0virtual:api-list') { + const files = await fs.readdir(API_ROOT) + + const names = files + .sort((a, b) => a.localeCompare(b)) + .map(file => `'${file.split('.')[0]}'`) + .join(', ') - await generateFiles() + return `export default [${names}]` + } }, } } diff --git a/packages/docs/build/frontmatterMeta.ts b/packages/docs/build/frontmatterMeta.ts new file mode 100644 index 00000000000..8ffb802a0d3 --- /dev/null +++ b/packages/docs/build/frontmatterMeta.ts @@ -0,0 +1,181 @@ +import { createBuilder } from '@yankeeinlondon/builder-api' +import type { Pipeline } from '@yankeeinlondon/builder-api' +import type { Plugin } from 'vite' +import Ajv from 'ajv' +import { md } from './markdown-it' +import fm from 'front-matter' +import fs from 'node:fs' +import path from 'node:path' + +const ajv = new Ajv() +const validate = ajv.compile({ + type: 'object', + additionalProperties: false, + properties: { + meta: { + type: 'object', + additionalProperties: false, + properties: { + nav: { type: 'string' }, // Title used in navigation links + title: { type: 'string' }, // SEO title + description: { type: 'string' }, // SEO description + keywords: { type: 'string' }, // SEO keywords + }, + }, + layout: { type: 'string' }, + related: { + type: 'array', + maxItems: 3, + uniqueItems: true, + items: { type: 'string' }, // Absolute paths to related pages + }, + assets: { + type: 'array', + uniqueItems: true, + items: { type: 'string' }, // Additional stylesheets to load + }, + disabled: { type: 'boolean' }, // The page is not published + emphasized: { type: 'boolean' }, // The page is emphasized in the navigation + fluid: { type: 'boolean' }, // Hide the Toc + backmatter: { type: 'boolean' }, // Hide the backmatter + features: { + type: 'object', + additionalProperties: false, + properties: { + figma: { type: 'boolean' }, + label: { type: 'string' }, + report: { type: 'boolean' }, + github: { type: 'string' }, + spec: { type: 'string' }, + }, + }, + }, +}) + +export const frontmatterBuilder = createBuilder('frontmatterBuilder', 'metaExtracted') + .options<{ + files?: Map> + awaiting?: Map) => void)[]> + pages?: ReadonlyArray> + }>() + .initializer() + .handler(async (payload, options) => { + const { frontmatter, fileName } = payload + + if (!options.pages) { + const pagesPlugin = payload.viteConfig.plugins! + .find(p => p && 'name' in p && p.name === 'vite-plugin-pages') as Plugin + options.pages = await pagesPlugin.api.getResolvedRoutes() as [] + } + + const page = options.pages.find(p => fileName.endsWith(p.component)) + + if (!page) throw new Error('Unable to find page') + + const locale = page.path.split('/').at(0)! + + options.files ??= new Map() + options.files.set(page.path, payload) + + const { meta, ...rest } = frontmatter + + if (locale !== 'en') { + const originalPath = page.path.replace(`/${locale}/`, '/en/') + let original = options.files.get(originalPath) + if (!original) { + options.awaiting ??= new Map() + const awaiting = options.awaiting.get(originalPath) ?? [] + const { promise, resolve } = Promise.withResolvers>() + awaiting.push(resolve) + options.awaiting.set(originalPath, awaiting) + original = await promise + } + Object.assign(rest, { + assets: original.frontmatter.assets, + related: original.frontmatter.related, + }) + } + + if (options.awaiting?.has(page.path)) { + options.awaiting.get(page.path)!.forEach(fn => fn(payload)) + } + + payload.frontmatter = { + meta, + assets: rest.assets, + backmatter: rest.backmatter, + features: rest.features, + fluid: rest.fluid, + related: rest.related, + toc: generateToc(payload.md), + } + + return payload + }) + .meta() + +export function getRouteMeta (componentPath: string, locale: string) { + const str = fs.readFileSync(path.resolve(componentPath.slice(1)), { encoding: 'utf-8' }) + const { attributes } = fm(str) + + const valid = validate(attributes) + if (!valid && locale !== 'eo-UY') { + throw new Error(`\nInvalid frontmatter: ${componentPath}` + validate.errors!.map(error => ( + `\n | Property ${error.instancePath} ${error.message}` + )).join()) + } + + const a = attributes as any + + if (locale !== 'en') { + const original = getRouteMeta(componentPath.replace(`/${locale}/`, '/en/'), 'en') + a.disabled = original.disabled + a.emphasized = original.emphasized + a.layout = original.layout + } + + return { + disabled: a.disabled, + emphasized: a.emphasized, + layout: a.layout, + ...a.meta, + } +} + +function generateToc (content: string) { + const headings = [] + const tokens = md.parse(content, {}) + const length = tokens.length + + for (let i = 0; i < length; i++) { + const token = tokens[i] + + if (token.type === 'inline' && token.content.startsWith('')) { + do { + i++ + } while (i < length && !tokens[i].content.endsWith('-->')) + continue + } + + if (token.type !== 'heading_open') continue + + // heading level by hash length '###' === h3 + const level = token.markup.length + + if (level <= 1) continue + + const next = tokens[i + 1] + const link = next.children?.find(child => child.type === 'link_open') + const text = next.children?.filter(child => !!child.content).map(child => child.content).join('') + const anchor = link?.attrs?.find(([attr]) => attr === 'href') + const [, to] = anchor ?? [] + + headings.push({ + text, + to, + level, + }) + } + + return headings +} diff --git a/packages/docs/build/markdown-it.ts b/packages/docs/build/markdown-it.ts index ddeee9b98ab..c03866abb6b 100644 --- a/packages/docs/build/markdown-it.ts +++ b/packages/docs/build/markdown-it.ts @@ -1,123 +1,5 @@ -import fs from 'fs' -import path from 'path' -import Ajv from 'ajv' -import fm from 'front-matter' import MarkdownIt from 'markdown-it' import { configureMarkdown } from '../src/utils/markdown-it' export { configureMarkdown } from '../src/utils/markdown-it' export const md = configureMarkdown(new MarkdownIt()) - -const generateToc = (content: string) => { - const headings = [] - const tokens = md.parse(content, {}) - const length = tokens.length - - for (let i = 0; i < length; i++) { - const token = tokens[i] - - if (token.type === 'inline' && token.content.startsWith('')) { - do { - i++ - } while (i < length && !tokens[i].content.endsWith('-->')) - continue - } - - if (token.type !== 'heading_open') continue - - // heading level by hash length '###' === h3 - const level = token.markup.length - - if (level <= 1) continue - - const next = tokens[i + 1] - const link = next.children?.find(child => child.type === 'link_open') - const text = next.children?.filter(child => !!child.content).map(child => child.content).join('') - const anchor = link?.attrs?.find(([attr]) => attr === 'href') - const [, to] = anchor ?? [] - - headings.push({ - text, - to, - level, - }) - } - - return headings -} - -const ajv = new Ajv() -const validate = ajv.compile({ - type: 'object', - additionalProperties: false, - properties: { - meta: { - type: 'object', - additionalProperties: false, - properties: { - nav: { type: 'string' }, // Title used in navigation links - title: { type: 'string' }, // SEO title - description: { type: 'string' }, // SEO description - keywords: { type: 'string' }, // SEO keywords - }, - }, - layout: { type: 'string' }, - related: { - type: 'array', - maxItems: 3, - uniqueItems: true, - items: { type: 'string' }, // Absolute paths to related pages - }, - assets: { - type: 'array', - uniqueItems: true, - items: { type: 'string' }, // Additional stylesheets to load - }, - disabled: { type: 'boolean' }, // The page is not published - emphasized: { type: 'boolean' }, // The page is emphasized in the navigation - fluid: { type: 'boolean' }, // Hide the Toc - backmatter: { type: 'boolean' }, // Hide the backmatter - features: { - type: 'object', - additionalProperties: false, - properties: { - figma: { type: 'boolean' }, - label: { type: 'string' }, - report: { type: 'boolean' }, - github: { type: 'string' }, - spec: { type: 'string' }, - }, - }, - }, -}) - -export function parseMeta (componentPath: string, locale: string) { - const str = fs.readFileSync(path.resolve(componentPath.slice(1)), { encoding: 'utf-8' }) - const { attributes, body } = fm(str) - - const valid = validate(attributes) - if (!valid && locale !== 'eo-UY') { - throw new Error(`\nInvalid frontmatter: ${componentPath}` + validate.errors!.map(error => ( - `\n | Property ${error.instancePath} ${error.message}` - )).join()) - } - - const { meta, ...rest } = attributes as any - - if (locale !== 'en') { - const original = parseMeta(componentPath.replace(`/${locale}/`, '/en/'), 'en') - Object.assign(rest, { - layout: original.layout, - related: original.related, - assets: original.assets, - disabled: original.disabled, - emphasized: original.emphasized, - }) - } - - return { - ...rest, - ...meta, - toc: generateToc(body), - } -} diff --git a/packages/docs/build/mdi-js.ts b/packages/docs/build/mdi-js.ts new file mode 100644 index 00000000000..0d1e5c5e770 --- /dev/null +++ b/packages/docs/build/mdi-js.ts @@ -0,0 +1,31 @@ +import { camelize } from 'vue' +import type { Plugin } from 'vite' + +const virtual = 'virtual:mdi-js-icons' +const resolvedVirtual = `\0${virtual}` + +export function MdiJs () { + return { + name: 'vuetify:mdi-js-icons', + enforce: 'pre', + resolveId (id) { + return id === virtual ? resolvedVirtual : undefined + }, + async load (id) { + if (id === resolvedVirtual) { + const [meta, paths] = await Promise.all([ + import('@mdi/svg/meta.json', { with: { type: 'json' } }).then(m => m.default), + import('@mdi/js'), + ]) + const icons = meta.map(icon => ({ + name: icon.name, + aliases: icon.aliases, + path: paths[camelize('mdi-' + icon.name) as keyof typeof paths], + })) + return `export const icons = ${JSON.stringify(icons)}` + } + + return undefined + }, + } satisfies Plugin +} diff --git a/packages/docs/components.d.ts b/packages/docs/components.d.ts index a3dd894b29d..34b2633b0c7 100644 --- a/packages/docs/components.d.ts +++ b/packages/docs/components.d.ts @@ -1,16 +1,17 @@ /* eslint-disable */ -/* prettier-ignore */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 export {} +/* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { AboutTeamMember: typeof import('./src/components/about/TeamMember.vue')['default'] AboutTeamMembers: typeof import('./src/components/about/TeamMembers.vue')['default'] Alert: typeof import('./src/components/Alert.vue')['default'] ApiApiTable: typeof import('./src/components/api/ApiTable.vue')['default'] + ApiBacklinks: typeof import('./src/components/api/Backlinks.vue')['default'] ApiDirectiveTable: typeof import('./src/components/api/DirectiveTable.vue')['default'] ApiEventsTable: typeof import('./src/components/api/EventsTable.vue')['default'] ApiExposedTable: typeof import('./src/components/api/ExposedTable.vue')['default'] @@ -23,6 +24,7 @@ declare module 'vue' { ApiSearch: typeof import('./src/components/api/Search.vue')['default'] ApiSection: typeof import('./src/components/api/Section.vue')['default'] ApiSlotsTable: typeof import('./src/components/api/SlotsTable.vue')['default'] + ApiView: typeof import('./src/components/api/View.vue')['default'] AppBackToTop: typeof import('./src/components/app/BackToTop.vue')['default'] AppBarBar: typeof import('./src/components/app/bar/Bar.vue')['default'] AppBarEcosystemMenu: typeof import('./src/components/app/bar/EcosystemMenu.vue')['default'] @@ -53,10 +55,10 @@ declare module 'vue' { AppLink: typeof import('./src/components/app/Link.vue')['default'] AppListLinkListItem: typeof import('./src/components/app/list/LinkListItem.vue')['default'] AppListList: typeof import('./src/components/app/list/List.vue')['default'] - AppMarkdown: typeof import('./src/components/app/Markdown.vue')['default'] AppMarkup: typeof import('./src/components/app/Markup.vue')['default'] AppMenuMenu: typeof import('./src/components/app/menu/Menu.vue')['default'] AppSearchSearch: typeof import('./src/components/app/search/Search.vue')['default'] + AppSearchSearchDialog: typeof import('./src/components/app/search/SearchDialog.vue')['default'] AppSearchSearchRecent: typeof import('./src/components/app/search/SearchRecent.vue')['default'] AppSearchSearchResults: typeof import('./src/components/app/search/SearchResults.vue')['default'] AppSettingsAdvancedOptions: typeof import('./src/components/app/settings/AdvancedOptions.vue')['default'] diff --git a/packages/docs/jest.config.js b/packages/docs/jest.config.js index 7d57ee7901a..a24743350b9 100644 --- a/packages/docs/jest.config.js +++ b/packages/docs/jest.config.js @@ -1,6 +1,6 @@ const os = require('os') -const maxWorkers = Math.max(1, Math.floor(Math.min(os.cpus().length / 2, os.freemem() / 1024 / 1024 / 1024 / 2.5))) +const maxWorkers = Math.max(1, Math.floor(Math.min(os.cpus().length / 2, os.freemem() / 1024 ** 3 / 3.1))) module.exports = { maxWorkers, diff --git a/packages/docs/package.json b/packages/docs/package.json index dc79e68a195..de38f41ec65 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -21,9 +21,10 @@ }, "dependencies": { "@cosmicjs/sdk": "^1.0.11", + "@vue/compiler-dom": "^3.4.27", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", - "@vuetify/one": "^1.9.1", + "@vuetify/one": "^1.9.2", "algoliasearch": "^4.23.3", "fflate": "^0.8.2", "isomorphic-fetch": "^3.0.0", @@ -41,8 +42,14 @@ "vuetify": "workspace:*" }, "devDependencies": { + "@babel/generator": "^7.25.0", + "@babel/types": "^7.25.2", "@emailjs/browser": "^4.3.3", "@intlify/unplugin-vue-i18n": "^4.0.0", + "@mdi/js": "7.4.47", + "@mdi/svg": "7.4.47", + "@octokit/openapi-types": "^22.2.0", + "@types/babel__generator": "^7.6.8", "@types/lodash-es": "^4.17.12", "@types/markdown-it": "^14.0.0", "@types/markdown-it-container": "^2.0.10", @@ -51,7 +58,9 @@ "@vitejs/plugin-vue": "^5.0.4", "@vue/compiler-sfc": "^3.4.27", "@vuetify/api-generator": "workspace:*", + "@yankeeinlondon/builder-api": "^1.4.1", "ajv": "^8.12.0", + "algoliasearch-helper": "^3.22.3", "async-es": "^3.2.5", "date-fns": "^3.6.0", "emailjs-com": "^3.2.0", @@ -71,14 +80,14 @@ "markdownlint-cli": "^0.39.0", "unplugin-auto-import": "0.17.5", "unplugin-fonts": "1.0.3", - "unplugin-vue-components": "^0.26.0", - "vite": "^5.2.8", + "unplugin-vue-components": "^0.27.4", + "vite": "5.4.0-beta.1", "vite-plugin-md": "^0.21.5", "vite-plugin-pages": "^0.32.1", "vite-plugin-pwa": "^0.17.4", "vite-plugin-vue-layouts": "^0.11.0", - "vite-plugin-vuetify": "^2.0.3", - "vue-tsc": "^1.8.27" + "vite-plugin-vuetify": "^2.0.4", + "vue-tsc": "^2.0.29" }, "publishConfig": { "access": "public" diff --git a/packages/docs/src/App.vue b/packages/docs/src/App.vue index dc7b5caf2be..9412f7768b1 100644 --- a/packages/docs/src/App.vue +++ b/packages/docs/src/App.vue @@ -16,15 +16,16 @@ const theme = useTheme() const { locale } = useI18n() const auth = useAuthStore() + const frontmatter = useFrontmatter() const path = computed(() => route.path.replace(`/${locale.value}/`, '')) const meta = computed(() => { return genAppMetaInfo({ title: `${route.meta.title}${path.value === '' ? '' : ' — Vuetify'}`, - description: route.meta.description, - keywords: route.meta.keywords, - assets: route.meta.assets, + description: frontmatter.value?.meta.description, + keywords: frontmatter.value?.meta.keywords, + assets: frontmatter.value?.assets, }) }) diff --git a/packages/docs/src/components/PageFeatures.vue b/packages/docs/src/components/PageFeatures.vue index 66346b5bdc2..4a79a0064ca 100644 --- a/packages/docs/src/components/PageFeatures.vue +++ b/packages/docs/src/components/PageFeatures.vue @@ -12,7 +12,7 @@ { - if (!route.meta.features?.label) return false + if (!frontmatter.value?.features?.label) return false - const original = encodeURIComponent(route.meta.features.label) + const original = encodeURIComponent(frontmatter.value.features.label) return `https://github.com/vuetifyjs/vuetify/labels/${original}` }) @@ -103,7 +104,7 @@ pins.toggle(!pinned.value, { title: route.meta.title, to: route.path, - category: route.meta.category, + category: frontmatter.value?.category, }) } diff --git a/packages/docs/src/components/api/Backlinks.vue b/packages/docs/src/components/api/Backlinks.vue new file mode 100644 index 00000000000..8609ca04ad8 --- /dev/null +++ b/packages/docs/src/components/api/Backlinks.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/docs/src/components/api/Inline.vue b/packages/docs/src/components/api/Inline.vue index 745aaf091ea..532a8cf466c 100644 --- a/packages/docs/src/components/api/Inline.vue +++ b/packages/docs/src/components/api/Inline.vue @@ -29,7 +29,6 @@ @@ -49,7 +48,7 @@ const { t, locale } = useI18n() const user = useUserStore() const name = ref() - const sections = ['props', 'slots', 'events', 'functions'] + const sections = ['props', 'slots', 'events', 'exposed'] as const const components = computed(() => { if (props.components) return props.components.split(/, ?/) diff --git a/packages/docs/src/components/api/Section.vue b/packages/docs/src/components/api/Section.vue index f586345d7ac..688da67b67e 100644 --- a/packages/docs/src/components/api/Section.vue +++ b/packages/docs/src/components/api/Section.vue @@ -8,7 +8,7 @@ @input="filter = $event" /> --> - + @@ -45,7 +45,6 @@ type: String as PropType, required: true, }, - showHeadline: Boolean, }) const store = useLocaleStore() diff --git a/packages/docs/src/components/api/View.vue b/packages/docs/src/components/api/View.vue new file mode 100644 index 00000000000..c71bd344f4e --- /dev/null +++ b/packages/docs/src/components/api/View.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/docs/src/components/app/Markdown.vue b/packages/docs/src/components/app/Markdown.vue index 05098706840..88b551085db 100644 --- a/packages/docs/src/components/app/Markdown.vue +++ b/packages/docs/src/components/app/Markdown.vue @@ -7,6 +7,19 @@ diff --git a/packages/docs/src/components/app/Toc.vue b/packages/docs/src/components/app/Toc.vue index 5daa54a2174..b15dc95df0a 100644 --- a/packages/docs/src/components/app/Toc.vue +++ b/packages/docs/src/components/app/Toc.vue @@ -9,11 +9,9 @@ floating sticky > - @@ -56,15 +55,14 @@ diff --git a/packages/docs/src/components/doc/RelatedPages.vue b/packages/docs/src/components/doc/RelatedPages.vue index f48ac71df57..2e893bdb708 100644 --- a/packages/docs/src/components/doc/RelatedPages.vue +++ b/packages/docs/src/components/doc/RelatedPages.vue @@ -16,5 +16,6 @@ diff --git a/packages/docs/src/composables/frontmatter.ts b/packages/docs/src/composables/frontmatter.ts new file mode 100644 index 00000000000..b9a8bd2e256 --- /dev/null +++ b/packages/docs/src/composables/frontmatter.ts @@ -0,0 +1,39 @@ +type Frontmatter = { + meta: { + nav?: string + title: string + description?: string + keywords?: string[] + } + assets?: string[] + backmatter?: boolean + features?: { + figma?: boolean + label?: string + report?: string + github?: string + spec?: string + } + fluid?: boolean + related?: string[] + toc?: TocItem[] +} + +type TocItem = { + to: string + text: string + level: number +} + +export function useFrontmatter () { + const router = useRouter() + + const frontmatter = shallowRef() + watch(router.currentRoute, route => { + setTimeout(() => { + frontmatter.value = (route.matched.at(-1)!.instances.default as any)?.frontmatter + }) + }, { immediate: true }) + + return readonly(frontmatter) +} diff --git a/packages/docs/src/examples/v-overlay/misc-advanced.vue b/packages/docs/src/examples/v-overlay/misc-advanced.vue index 0885b31ba74..84e6901fa48 100644 --- a/packages/docs/src/examples/v-overlay/misc-advanced.vue +++ b/packages/docs/src/examples/v-overlay/misc-advanced.vue @@ -28,7 +28,7 @@ + const name = shallowRef('') + + +# {{ name }} API + + + + + + + + + + diff --git a/packages/docs/src/plugins/global-components.ts b/packages/docs/src/plugins/global-components.ts index f984e7ce05d..324f260af4c 100644 --- a/packages/docs/src/plugins/global-components.ts +++ b/packages/docs/src/plugins/global-components.ts @@ -1,102 +1,9 @@ -// Components -import AboutTeamMembers from '@/components/about/TeamMembers.vue' -import Alert from '@/components/Alert.vue' -import ApiInline from '@/components/api/Inline.vue' -import ApiSearch from '@/components/api/Search.vue' -import ApiSection from '@/components/api/Section.vue' -import AppDivider from '@/components/app/Divider.vue' -import AppHeading from '@/components/app/Heading.vue' -import AppFigure from '@/components/app/Figure.vue' -import AppMarkup from '@/components/app/Markup.vue' -import AppSettingsAdvancedOptions from '@/components/app/settings/AdvancedOptions.vue' -import AppSettingsOptions from '@/components/app/settings/Options.vue' -import AppSettingsPerksOptions from '@/components/app/settings/PerksOptions.vue' -import AppLink from '@/components/app/Link.vue' -import AppTable from '@/components/app/Table.vue' -import ComponentsListItem from '@/components/components/ListItem.vue' -import DocExplorer from '@/components/doc/Explorer.vue' -import DocIconList from '@/components/doc/IconList.vue' -import DocIconTable from '@/components/doc/IconTable.vue' -import DocTabs from '@/components/doc/Tabs.vue' -import DocVueJobs from '@/components/doc/VueJobs.vue' -import DocMadeWithVueAttribution from '@/components/doc/MadeWithVueAttribution.vue' -import DocMadeWithVuetifyGallery from '@/components/doc/MadeWithVuetifyGallery.vue' -import DocMadeWithVuetifyLink from '@/components/doc/MadeWithVuetifyLink.vue' -import DocPremiumThemesGallery from '@/components/doc/PremiumThemesGallery.vue' -import DocReleases from '@/components/doc/Releases.vue' -import DocThemeVendor from '@/components/doc/ThemeVendor.vue' -import ExamplesExample from '@/components/examples/Example.vue' -import ExamplesUsage from '@/components/examples/Usage.vue' -import FeaturesBreakpointsTable from '@/components/features/BreakpointsTable.vue' -import FeaturesColorPalette from '@/components/features/ColorPalette.vue' -import FeaturesSassApi from '@/components/features/SassApi.vue' -import GettingStartedWireframeExamples from '@/components/getting-started/WireframeExamples.vue' -import HomeEntry from '@/components/home/Entry.vue' -import HomeFeatures from '@/components/home/Features.vue' -import HomeSpecialSponsor from '@/components/home/SpecialSponsor.vue' -import HomeSponsors from '@/components/home/Sponsors.vue' -import IntroductionSlaDeck from '@/components/introduction/SlaDeck.vue' -import IntroductionDiscordDeck from '@/components/introduction/DiscordDeck.vue' -import IntroductionEnterpriseDeck from '@/components/introduction/EnterpriseDeck.vue' -import PageFeatures from '@/components/PageFeatures.vue' -import PromotedEntry from '@/components/promoted/Entry.vue' -import PromotedPromoted from '@/components/promoted/Promoted.vue' -import PromotedRandom from '@/components/promoted/Random.vue' -import ResourcesColorPalette from '@/components/resources/ColorPalette.vue' -import ResourcesLogos from '@/components/resources/Logos.vue' -import SponserSponsors from '@/components/sponsor/Sponsors.vue' +import { defineAsyncComponent } from 'vue' // Types import type { App } from 'vue' export function installGlobalComponents (app: App) { app - // Used by markdown-it to gen api pages, and needed to be globally loaded to work - .component('AboutTeamMembers', AboutTeamMembers) - .component('Alert', Alert) - .component('ApiInline', ApiInline) - .component('ApiSearch', ApiSearch) - .component('ApiSection', ApiSection) - .component('AppDivider', AppDivider) - .component('AppFigure', AppFigure) - .component('AppHeading', AppHeading) - .component('AppLink', AppLink) - .component('AppMarkup', AppMarkup) - .component('AppTable', AppTable) - .component('AppSettingsAdvancedOptions', AppSettingsAdvancedOptions) - .component('AppSettingsOptions', AppSettingsOptions) - .component('AppSettingsPerksOptions', AppSettingsPerksOptions) - .component('ComponentsListItem', ComponentsListItem) - .component('DocExplorer', DocExplorer) - .component('DocIconList', DocIconList) - .component('DocIconTable', DocIconTable) - .component('DocTabs', DocTabs) - .component('DocVueJobs', DocVueJobs) - .component('DocMadeWithVueAttribution', DocMadeWithVueAttribution) - .component('DocMadeWithVuetifyGallery', DocMadeWithVuetifyGallery) - .component('DocMadeWithVuetifyLink', DocMadeWithVuetifyLink) - .component('DocPremiumThemesGallery', DocPremiumThemesGallery) - .component('DocReleases', DocReleases) - .component('DocThemeVendor', DocThemeVendor) - .component('ExamplesExample', ExamplesExample) - .component('ExamplesUsage', ExamplesUsage) - .component('FeaturesBreakpointsTable', FeaturesBreakpointsTable) - .component('FeaturesColorPalette', FeaturesColorPalette) - .component('FeaturesSassApi', FeaturesSassApi) - .component('GettingStartedWireframeExamples', GettingStartedWireframeExamples) - .component('HomeEntry', HomeEntry) - .component('HomeFeatures', HomeFeatures) - .component('HomeSpecialSponsor', HomeSpecialSponsor) - .component('HomeSponsors', HomeSponsors) - .component('IntroductionSlaDeck', IntroductionSlaDeck) - .component('IntroductionDiscordDeck', IntroductionDiscordDeck) - .component('IntroductionEnterpriseDeck', IntroductionEnterpriseDeck) - .component('PageFeatures', PageFeatures) - .component('PromotedEntry', PromotedEntry) - .component('PromotedPromoted', PromotedPromoted) - .component('PromotedRandom', PromotedRandom) - .component('ResourcesColorPalette', ResourcesColorPalette) - .component('ResourcesLogos', ResourcesLogos) - .component('SponserSponsors', SponserSponsors) - .component('UnwrapMarkdown', (props, { slots }) => slots.default?.()?.[0].children) + .component('AppMarkdown', defineAsyncComponent(() => import('@/components/app/Markdown.vue'))) } diff --git a/packages/docs/src/plugins/icons.ts b/packages/docs/src/plugins/icons.ts index 5f50e64d639..12d28b89216 100644 --- a/packages/docs/src/plugins/icons.ts +++ b/packages/docs/src/plugins/icons.ts @@ -118,6 +118,7 @@ export { mdiContentSaveCogOutline, mdiControllerClassicOutline, mdiCookie, + mdiCreationOutline, mdiCreditCardOutline, mdiCrosshairsGps, mdiCrown, @@ -127,7 +128,6 @@ export { mdiDeleteOutline, mdiDesktopTowerMonitor, mdiDialpad, - mdiDiscord, mdiDomain, mdiDotsHorizontal, mdiDotsVertical, @@ -184,8 +184,6 @@ export { mdiFormatHeader1, mdiFormatItalic, mdiFormatListBulletedSquare, - mdiFormatTextdirectionLToR, - mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiFormatWrapInline, mdiForum, @@ -399,4 +397,6 @@ export { mdiWrench, } from '@mdi/js' -export const mdiCreationOutline = 'M9 4L11.5 9.5L17 12L11.5 14.5L9 20L6.5 14.5L1 12L6.5 9.5L9 4M9 8.83L8 11L5.83 12L8 13L9 15.17L10 13L12.17 12L10 11L9 8.83M19 9L17.74 6.26L15 5L17.74 3.75L19 1L20.25 3.75L23 5L20.25 6.26L19 9M19 23L17.74 20.26L15 19L17.74 17.75L19 15L20.25 17.75L23 19L20.25 20.26L19 23Z' +export const mdiDiscord = 'M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z' +export const mdiFormatTextdirectionLToR = 'M21,18L17,14V17H5V19H17V22M9,10V15H11V4H13V15H15V4H17V2H9A4,4 0 0,0 5,6A4,4 0 0,0 9,10Z' +export const mdiFormatTextdirectionRToL = 'M8,17V14L4,18L8,22V19H20V17M10,10V15H12V4H14V15H16V4H18V2H10A4,4 0 0,0 6,6A4,4 0 0,0 10,10Z' diff --git a/packages/docs/src/plugins/vuetify.ts b/packages/docs/src/plugins/vuetify.ts index 49ca8a3f19f..dc04e625a3d 100644 --- a/packages/docs/src/plugins/vuetify.ts +++ b/packages/docs/src/plugins/vuetify.ts @@ -3,9 +3,10 @@ import 'vuetify/styles' // Imports import { createVuetify } from 'vuetify' -import * as components from 'vuetify/components' -import * as directives from 'vuetify/directives' -import * as labs from 'vuetify/labs/components' +import { VChip } from 'vuetify/components/VChip' +import { VBtn } from 'vuetify/components/VBtn' +import { VSwitch } from 'vuetify/components/VSwitch' +import { VSvgIcon } from 'vuetify/components/VIcon' // Icons import { fa } from 'vuetify/iconsets/fa' @@ -24,17 +25,12 @@ import type { IconProps } from 'vuetify' export function installVuetify (app: App) { const vuetify = createVuetify({ aliases: { - BorderChip: components.VChip, - NewInChip: components.VChip, - PageFeatureChip: components.VChip, - PrimaryBtn: components.VBtn, - SettingsSwitch: components.VSwitch, + BorderChip: VChip, + NewInChip: VChip, + PageFeatureChip: VChip, + PrimaryBtn: VBtn, + SettingsSwitch: VSwitch, }, - components: { - ...components, - ...labs, - }, - directives, defaults: { global: { eager: false, @@ -109,7 +105,7 @@ export function installVuetify (app: App) { mdi: { component: (props: IconProps) => { const icon = mdiSvg[camelize(props.icon as string) as keyof typeof mdiSvg] - return h(components.VSvgIcon, { ...props, icon }) + return h(VSvgIcon, { ...props, icon }) }, }, }, diff --git a/packages/docs/src/shims.d.ts b/packages/docs/src/shims.d.ts index 8b24f072d17..fdfa6f3c1bc 100644 --- a/packages/docs/src/shims.d.ts +++ b/packages/docs/src/shims.d.ts @@ -10,17 +10,31 @@ declare module '*.vue' { export default component } +declare module 'virtual:mdi-js-icons' { + export interface IconEntry { + name: string + aliases: string[] + path: string + } + export const icons: IconEntry[] +} + +declare module 'virtual:api-list' { + const list: string[] + export default list +} + declare module 'markdown-it-header-sections' { - import type MarkdownIt from 'markdown-it' + import type { PluginSimple } from 'markdown-it' - const MarkdownItHeaderSections: MarkdownIt.PluginSimple + const MarkdownItHeaderSections: PluginSimple export default MarkdownItHeaderSections } declare module 'markdown-it-attrs' { - import type MarkdownIt from 'markdown-it' + import type { PluginWithOptions } from 'markdown-it' - const MarkdownItAttrs: MarkdownIt.PluginWithOptions<{ + const MarkdownItAttrs: PluginWithOptions<{ leftDelimiter?: string rightDelimiter?: string allowedAttributes?: string[] @@ -29,14 +43,14 @@ declare module 'markdown-it-attrs' { } declare module 'markdown-it-link-attributes' { - import type MarkdownIt from 'markdown-it' + import type { PluginWithOptions } from 'markdown-it' interface Config { pattern?: string attrs: Record } - const MarkdownItLinkAttributes: MarkdownIt.PluginWithOptions + const MarkdownItLinkAttributes: PluginWithOptions export default MarkdownItLinkAttributes } @@ -51,19 +65,4 @@ declare module 'virtual:examples' { }> } -declare module '@emailjs/browser' { - interface emailjs { - sendForm ( - service_id: string, - template_id: string, - el: HTMLFormElement, - public_key: string - ): Promise - } - - const client: emailjs - - export default client -} - declare module 'vue-instantsearch/vue3/es/src/instantsearch.js' diff --git a/packages/docs/src/stores/made-with-vuetify.ts b/packages/docs/src/stores/made-with-vuetify.ts index 0397bc019cc..23323405486 100644 --- a/packages/docs/src/stores/made-with-vuetify.ts +++ b/packages/docs/src/stores/made-with-vuetify.ts @@ -2,7 +2,9 @@ export const useMadeWithVuetifyStore = defineStore('made-with-vuetify', () => { const items = ref([]) onBeforeMount(async () => { - const res = await fetch('https://madewithvuejs.com/api/tag/vuetify') + const res = await fetch('https://madewithvuejs.com/api/tag/vuetify', { + priority: 'low', + }) .then(res => res.json()) items.value = res.data diff --git a/packages/docs/src/utils/helpers.ts b/packages/docs/src/utils/helpers.ts index 0e1a740c8eb..0f8bd0e8b1d 100644 --- a/packages/docs/src/utils/helpers.ts +++ b/packages/docs/src/utils/helpers.ts @@ -40,7 +40,7 @@ export async function waitForReadystate () { /** Jaro-Winkler distance between two strings */ // eslint-disable-next-line max-statements -export function distance (s1: string, s2: string) { +export function getDistance (s1: string, s2: string) { // Exit early if either are empty. if (s1.length === 0 || s2.length === 0) { return 0 diff --git a/packages/docs/tsconfig.json b/packages/docs/tsconfig.json index 433e6b2fe8a..33062e52aa9 100644 --- a/packages/docs/tsconfig.json +++ b/packages/docs/tsconfig.json @@ -9,10 +9,10 @@ "esnext" ], "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "noUnusedLocals": true, "paths": { - "@/*": ["src/*"], + "@/*": ["src/*"] }, "resolveJsonModule": true, "skipLibCheck": true, @@ -29,6 +29,12 @@ ], "outDir": "./dist" }, - "include": ["../api-generator/src/shims.d.ts"], + "include": [ + "build", + "src", + "./auto-imports.d.ts", + "./components.d.ts", + "../api-generator/src/shims.d.ts" + ], "exclude": ["dist", "node_modules"], } diff --git a/packages/docs/vite.config.mts b/packages/docs/vite.config.mts index 4f651b15b23..0d777aa0d4d 100644 --- a/packages/docs/vite.config.mts +++ b/packages/docs/vite.config.mts @@ -15,11 +15,14 @@ import VueI18n from '@intlify/unplugin-vue-i18n/vite' import Inspect from 'vite-plugin-inspect' import Vuetify from 'vite-plugin-vuetify' import basicSsl from '@vitejs/plugin-basic-ssl' +import MagicString from 'magic-string' -import { configureMarkdown, parseMeta } from './build/markdown-it' +import { configureMarkdown } from './build/markdown-it' import Api from './build/api-plugin' import { Examples } from './build/examples-plugin' import { genAppMetaInfo } from './src/utils/metadata' +import { MdiJs } from './build/mdi-js' +import { frontmatterBuilder, getRouteMeta } from './build/frontmatterMeta' const resolve = (file: string) => fileURLToPath(new URL(file, import.meta.url)) @@ -41,7 +44,7 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { alias: [ { find: '@', replacement: `${resolve('src')}/` }, { find: 'node-fetch', replacement: 'isomorphic-fetch' }, - { find: /^vue$/, replacement: isSsrBuild ? 'vue' : 'vue/dist/vue.esm-bundler.js' }, + { find: /^vue$/, replacement: isSsrBuild ? 'vue' : 'vue/dist/vue.runtime.esm-bundler.js' }, { find: /^pinia$/, replacement: 'pinia/dist/pinia.mjs' }, ], }, @@ -49,11 +52,19 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { 'process.env.NODE_ENV': mode === 'production' || isSsrBuild ? '"production"' : '"development"', __INTLIFY_PROD_DEVTOOLS__: 'false', }, + css: { + preprocessorOptions: { + sass: { + api: 'modern' + } + }, + preprocessorMaxWorkers: true, + }, build: { - sourcemap: mode === 'development', + sourcemap: true, modulePreload: false, cssCodeSplit: false, - minify: false, + minify: true, rollupOptions: { output: isSsrBuild ? { inlineDynamicImports: true } : { // TODO: these options currently cause a request cascade @@ -69,15 +80,22 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { }, }, }, + esbuild: { + lineLimit: 1000, + }, plugins: [ // https://github.com/unplugin/unplugin-auto-import AutoImport({ + include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/], dirs: [ './src/composables/**', './src/stores/**', './src/utils/**', ], imports: [ + 'vue', + 'vue-router', + 'pinia', { '@vuetify/one': [ 'createOne', @@ -90,16 +108,10 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { 'useProductsStore', ], 'lodash-es': ['camelCase', 'kebabCase', 'upperFirst'], - pinia: ['defineStore', 'storeToRefs'], - vue: [ - 'camelize', 'computed', 'h', 'mergeProps', 'nextTick', - 'onBeforeMount', 'onBeforeUnmount', 'onMounted', 'onScopeDispose', 'onServerPrefetch', - 'ref', 'shallowRef', 'useAttrs', 'watch', 'watchEffect' - ], + vue: ['camelize', 'mergeProps'], vuetify: ['useDate', 'useDisplay', 'useGoTo', 'useRtl', 'useTheme'], 'vue-gtag-next': ['useGtag'], 'vue-i18n': ['useI18n'], - 'vue-router': ['onBeforeRouteLeave', 'onBeforeRouteUpdate', 'useRoute', 'useRouter'], } ], vueTemplate: true, @@ -120,7 +132,9 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { // https://github.com/antfu/unplugin-vue-components Components({ directoryAsNamespace: true, - include: [/\.vue$/, /\.vue\?vue/, /\.md$/], + include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.md\?vue/], + exclude: [], + excludeNames: ['AppMarkdown'], }), // https://github.com/JohnCampionJr/vite-plugin-vue-layouts @@ -133,10 +147,11 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { // https://github.com/antfu/vite-plugin-md Markdown({ - wrapperComponent: 'unwrap-markdown', wrapperClasses: '', - headEnabled: true, + exposeFrontmatter: true, + exposeExcerpt: false, markdownItSetup: configureMarkdown, + builders: [frontmatterBuilder()] }), // https://github.com/hannoeru/vite-plugin-pages @@ -144,7 +159,6 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { extensions: ['vue', 'md'], dirs: [ { dir: 'src/pages', baseRoute: '' }, - { dir: 'node_modules/.cache/api-pages', baseRoute: '' }, ], extendRoute (route) { let [locale, category, ...rest] = route.path.split('/').slice(1) @@ -152,10 +166,7 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { const idx = route.component.toLowerCase().indexOf(locale) locale = ~idx ? route.component.slice(idx, idx + locale.length) : locale - const meta = { - layout: 'default', - ...parseMeta(route.component, locale), - } + const meta = getRouteMeta(route.component, locale) if (meta.disabled) { return { disabled: true } @@ -164,18 +175,26 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { return { ...route, path: '/' + [locale, category, ...rest].filter(Boolean).join('/') + '/', - // name: [`${category ?? meta.layout}`, ...rest].join('-'), meta: { ...meta, category, - page: rest.join('-'), locale, }, } }, onRoutesGenerated (routes) { allRoutes = routes.filter(route => !route.disabled) - return allRoutes + return allRoutes.map(route => ({ + ...route, + meta: JSON.parse(JSON.stringify({ // remove undefined + category: route.meta.category, + emphasized: route.meta.emphasized, + layout: route.meta.layout, + locale: route.meta.locale, + nav: route.meta.nav, + title: route.meta.title, + })) + })) }, importMode (filepath) { return [ @@ -230,12 +249,20 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { const options = /(