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/.npmrc b/.npmrc index 0e5a898885a..76d5968aa83 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ auto-install-peers=false ignore-workspace-root-check=true +shell-emulator=true 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 0a694ef5e78..ead12e22124 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.19.0 || >=20.6.0" + "node": ">=22.0.0" }, "devDependencies": { - "@babel/cli": "^7.24.1", - "@babel/core": "^7.24.4", - "@babel/preset-env": "^7.24.4", - "@babel/preset-typescript": "^7.24.1", - "@mdi/font": "6.2.95", - "@mdi/js": "6.2.95", - "@mdi/svg": "6.2.95", + "@babel/cli": "^7.24.8", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/preset-typescript": "^7.24.7", + "@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", @@ -50,7 +50,6 @@ "conventional-changelog-cli": "^3.0.0", "conventional-changelog-vuetify": "^1.2.1", "conventional-github-releaser": "^3.1.5", - "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", "eslint": "^8.57.0", "eslint-config-standard": "^17.1.0", @@ -76,18 +75,24 @@ "moment": "^2.30.1", "rimraf": "^5.0.5", "sass": "^1.77.8", + "sass-embedded": "^1.77.8", "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.5.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 ab41e39a11a..f1e6745d156 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/api-generator/src/index.ts b/packages/api-generator/src/index.ts index 0521edd3a1f..f9f02295a49 100644 --- a/packages/api-generator/src/index.ts +++ b/packages/api-generator/src/index.ts @@ -1,8 +1,8 @@ import fs from 'fs/promises' import path from 'upath' import { components } from 'vuetify/dist/vuetify-labs.js' -import importMap from 'vuetify/dist/json/importMap.json' assert { type: 'json' } -import importMapLabs from 'vuetify/dist/json/importMap-labs.json' assert { type: 'json' } +import importMap from 'vuetify/dist/json/importMap.json' with { type: 'json' } +import importMapLabs from 'vuetify/dist/json/importMap-labs.json' with { type: 'json' } import { kebabCase } from './helpers/text' import type { BaseData, ComponentData, DirectiveData } from './types' import { generateComposableDataFromTypes, generateDirectiveDataFromTypes } from './types' diff --git a/packages/api-generator/src/locale/en/VListItem.json b/packages/api-generator/src/locale/en/VListItem.json index 61d1733f621..ce3ff1870dc 100644 --- a/packages/api-generator/src/locale/en/VListItem.json +++ b/packages/api-generator/src/locale/en/VListItem.json @@ -4,7 +4,7 @@ "color": "Applies specified color to the control when in an **active** state or **input-value** is **true** - supports utility colors (for example `success` or `purple`) or css color (`#033` or `rgba(255, 0, 0, 0.5)`). Find a list of built-in classes on the [colors page](/styles/colors#material-colors),", "contained": "Changes the component style by changing how color is applied to the background.", "title": "Generates a `v-list-item-title` component with the supplied value. Note that this overrides the native [`title`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/title) attribute, that must be set with `v-bind:title.attr` instead.", - "value": "The value used for selection.", + "value": "The value used for selection. Obtained from [`v-list`](/api/v-list)'s `v-model:selected` when the item is selected.", "lines": "The line declaration specifies the minimum height of the item and can also be controlled from v-list with the same prop.", "nav": "Reduces the width v-list-item takes up as well as adding a border radius.", "slim": "Reduces horizontal spacing for badges, icons, tooltips, and avatars to create a more compact visual representation." diff --git a/packages/api-generator/src/utils.ts b/packages/api-generator/src/utils.ts index b575f215039..ee04b07308c 100644 --- a/packages/api-generator/src/utils.ts +++ b/packages/api-generator/src/utils.ts @@ -108,7 +108,7 @@ async function loadLocale (componentName: string, locale: string): Promise { const getDocUrl = (cmp: string, heading?: string) => 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/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/markdownBuilders.ts b/packages/docs/build/markdownBuilders.ts new file mode 100644 index 00000000000..aaa8dac3846 --- /dev/null +++ b/packages/docs/build/markdownBuilders.ts @@ -0,0 +1,223 @@ +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' }, + }, + }, + }, +}) + +async function setupPages> ( + payload: T, + options: { + files?: Map + awaiting?: Map void)[]> + pages?: ReadonlyArray> + } +) { + if (!options.pages) { + const pagesPlugin = payload.viteConfig.plugins! + .find((p: any) => p && 'name' in p && p.name === 'vite-plugin-pages') as Plugin + options.pages = await pagesPlugin.api.getResolvedRoutes() as [] + } + + const page = options.pages.find(p => payload.fileName.endsWith(p.component)) + + if (!page) throw new Error('Unable to find page') + + const locale = page.path.split('/').at(1)! + + const html = 'html' in payload && typeof payload.html !== 'string' + ? payload.html.cloneNode(true) + : undefined + options.files ??= new Map() + options.files.set(page.path, { + ...payload, + html, + }) + + let original: T | undefined + if (locale !== 'en') { + const originalPath = page.path.replace(`/${locale}/`, '/en/') + 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 + } + } else { + original = payload + if (options.awaiting?.has(page.path)) { + options.awaiting.get(page.path)!.forEach(fn => fn({ + ...payload, + html, + })) + } + } + + return { page, locale, original } +} + +export const frontmatterBuilder = createBuilder('frontmatterBuilder', 'metaExtracted') + .options() + .initializer() + .handler(async (payload, options) => { + const { locale, original } = await setupPages(payload, options) + + const { meta, ...rest } = payload.frontmatter + + if (locale !== 'en') { + Object.assign(rest, { + assets: original.frontmatter.assets, + related: original.frontmatter.related, + }) + } + + 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 +} + +export const scriptFixer = createBuilder('scriptFixer', 'dom') + .options() + .initializer() + .handler(async (payload, options) => { + const { locale, original } = await setupPages(payload, options) + + if (locale !== 'en') { + const setup = payload.html.querySelector('script[setup]') + const origSetup = original.html.querySelector('script[setup]') + if (setup) { + if (!origSetup) { + throw new Error(`Extra setup in ${payload.fileName}`) + } + setup.innerHTML = origSetup.innerHTML + } + } + + return payload + }) + .meta() 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 39792f9f574..844a3f2967e 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", "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-form/misc-exposed.vue b/packages/docs/src/examples/v-form/misc-exposed.vue index 97d3eb8e373..c77c6612fb4 100644 --- a/packages/docs/src/examples/v-form/misc-exposed.vue +++ b/packages/docs/src/examples/v-form/misc-exposed.vue @@ -72,7 +72,7 @@ const name = ref('') const nameRules = ref([ v => !!v || 'Name is required', - v => (v && v.length <= 10) || 'Name must be less than 10 characters', + v => (v && v.length <= 10) || 'Name must be 10 characters or less', ]) const select = ref(null) const checkbox = ref(false) @@ -96,7 +96,7 @@ name: '', nameRules: [ v => !!v || 'Name is required', - v => (v && v.length <= 10) || 'Name must be less than 10 characters', + v => (v && v.length <= 10) || 'Name must be 10 characters or less', ], select: null, items: [ diff --git a/packages/docs/src/examples/v-form/misc-vee-validate.vue b/packages/docs/src/examples/v-form/misc-vee-validate.vue index 8d1d387019b..f1441dbc1c6 100644 --- a/packages/docs/src/examples/v-form/misc-vee-validate.vue +++ b/packages/docs/src/examples/v-form/misc-vee-validate.vue @@ -60,9 +60,9 @@ return 'Name needs to be at least 2 characters.' }, phone (value) { - if (value?.length > 9 && /[0-9-]+/.test(value)) return true + if (/^[0-9-]{7,}$/.test(value)) return true - return 'Phone number needs to be at least 9 digits.' + return 'Phone number needs to be at least 7 digits.' }, email (value) { if (/^[a-z.-]+@[a-z.-]+\.[a-z]+$/i.test(value)) return true diff --git a/packages/docs/src/examples/v-form/prop-fast-fail.vue b/packages/docs/src/examples/v-form/prop-fast-fail.vue index 661af32d0db..751889d0c42 100644 --- a/packages/docs/src/examples/v-form/prop-fast-fail.vue +++ b/packages/docs/src/examples/v-form/prop-fast-fail.vue @@ -24,7 +24,7 @@ const firstName = ref('') const firstNameRules = [ value => { - if (value?.length > 3) return true + if (value?.length >= 3) return true return 'First name must be at least 3 characters.' }, ] @@ -44,7 +44,7 @@ firstName: '', firstNameRules: [ value => { - if (value?.length > 3) return true + if (value?.length >= 3) return true return 'First name must be at least 3 characters.' }, 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/pages/en/features/display-and-platform.md b/packages/docs/src/pages/en/features/display-and-platform.md index ac247142438..d09871fd0df 100644 --- a/packages/docs/src/pages/en/features/display-and-platform.md +++ b/packages/docs/src/pages/en/features/display-and-platform.md @@ -220,7 +220,7 @@ Use the **useDisplay** composable alongside Vue 3's `setup` function to harness ### Breakpoint conditionals -Breakpoint and conditional values return a `boolean` that is derived from the current viewport size. Additionally, the **breakpoint** composable follows the [Vuetify Grid](/components/grids) naming conventions and has access to properties such as **xlOnly**, **xsOnly**, **mdAndDown**, and many others. In the following example we use the `setup` function to pass the _xs_ and _mdAndUp_ values to our template: +Breakpoint and conditional values return a `boolean` that is derived from the current viewport size. Additionally, the **breakpoint** composable follows the [Vuetify Grid](/components/grids) naming conventions and has access to properties such as **xs**, **smAndUp**, **mdAndDown**, and many others. In the following example we use the `setup` function to pass the _xs_ and _mdAndUp_ values to our template: ```html { resource="Component.vue" } ``` -You should now have access to all Vuetify components and tools in Nuxt app. +You should now have access to all Vuetify components and tools in the Nuxt app. + +### vuetify-nuxt-module + +Alternatively, you can use the [vuetify-nuxt-module](https://github.com/userquin/vuetify-nuxt-module) (works only with Vite). The module is strongly opinionated and has a built-in default configuration out of the box. You can use it without any configuration, and it will work for most use cases. + +Check the [documentation](https://vuetify-nuxt-module.netlify.app/) for more information on how to use it. ## Using Laravel Mix @@ -267,24 +277,24 @@ app.use(vuetify).mount('#app') You can use Vuetify's components in your Vitepress static site. -First, add vuetify to your dependencies +After initializing your Vitepress project, add Vuetify to your dependencies ::: tabs ```bash [pnpm] -pnpm create vuetify +pnpm i vuetify ``` ```bash [yarn] -yarn create vuetify +yarn add vuetify ``` ```bash [npm] -npm create vuetify@latest +npm i vuetify ``` ```bash [bun] -bun create vuetify +bun add vuetify ``` ::: diff --git a/packages/docs/src/pages/en/getting-started/upgrade-guide.md b/packages/docs/src/pages/en/getting-started/upgrade-guide.md index 61737ab9450..4062276e41d 100644 --- a/packages/docs/src/pages/en/getting-started/upgrade-guide.md +++ b/packages/docs/src/pages/en/getting-started/upgrade-guide.md @@ -70,6 +70,7 @@ app.use(vuetify) - Global styles previously included as `.v-application p` or `.v-application ul` are no longer included. If you need margin for `p`, or padding-left for `ul` and `ol`, set it manually in your root component's `