diff --git a/.github/actions/download-locales/action.yml b/.github/actions/download-locales/action.yml index db73d7b681ad..413566dad881 100644 --- a/.github/actions/download-locales/action.yml +++ b/.github/actions/download-locales/action.yml @@ -11,7 +11,7 @@ runs: using: composite steps: - name: Download eo-UY - uses: crowdin/github-action@v1.12.0 + uses: crowdin/github-action@v1.19.0 with: download_language: eo config: crowdin.yml @@ -21,7 +21,7 @@ runs: export_only_approved: false crowdin_branch_name: ${{ inputs.crowdin-branch }} - name: Download zh-CN - uses: crowdin/github-action@v1.12.0 + uses: crowdin/github-action@v1.19.0 with: download_language: zh-CN config: crowdin.yml diff --git a/.github/actions/nightly-release/action.yml b/.github/actions/nightly-release/action.yml index a36f3e3289b3..fe8d525545f9 100644 --- a/.github/actions/nightly-release/action.yml +++ b/.github/actions/nightly-release/action.yml @@ -30,7 +30,7 @@ runs: git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" shell: bash - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: repository: ${{ inputs.checkout-repo }} ref: ${{ inputs.checkout-ref }} diff --git a/.github/actions/yarn-install/action.yml b/.github/actions/yarn-install/action.yml index aff639a366fb..4148990647f4 100644 --- a/.github/actions/yarn-install/action.yml +++ b/.github/actions/yarn-install/action.yml @@ -4,7 +4,7 @@ description: Restore node_modules and cache, then run yarn install runs: using: composite steps: - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | node_modules diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afe39e488031..ed13ae181050 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: if: needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/yarn-install - run: yarn build vuetify - uses: ./.github/actions/upload-artifact @@ -50,7 +50,7 @@ jobs: matrix: scopes: ['--scope vuetify --scope @vuetify/api-generator', '--scope vuetifyjs.com'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/download-artifact with: name: vuetify-dist @@ -65,11 +65,11 @@ jobs: if: needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/yarn-install - run: yarn run test:coverage -i working-directory: ./packages/vuetify - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 test-cypress: name: Test (Cypress) @@ -77,7 +77,7 @@ jobs: if: needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/yarn-install - run: yarn cy:run --record --parallel --ci-build-id $GITHUB_RUN_ID if: ${{ !startswith(github.ref, 'refs/tags/v') && github.repository_owner == 'vuetifyjs' }} @@ -87,7 +87,7 @@ jobs: - run: yarn cy:run if: ${{ !startswith(github.ref, 'refs/tags/v') && github.repository_owner != 'vuetifyjs' }} working-directory: ./packages/vuetify - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: failure() with: name: cypress-screenshots @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/v') && github.repository_owner == 'vuetifyjs' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: ./.github/actions/download-artifact @@ -125,7 +125,7 @@ jobs: if: needs.pre_job.outputs.should_skip != 'true' && github.event_name == 'push' && github.repository_owner == 'vuetifyjs' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/next') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/download-artifact with: name: vuetify-dist @@ -156,7 +156,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'push' && github.repository_owner == 'vuetifyjs' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/next') steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ./.github/actions/download-artifact with: name: docs-dist diff --git a/.github/workflows/crowdin-uploads.yml b/.github/workflows/crowdin-uploads.yml index 45d851527f9a..01e839d9f18e 100644 --- a/.github/workflows/crowdin-uploads.yml +++ b/.github/workflows/crowdin-uploads.yml @@ -26,10 +26,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Upload - uses: crowdin/github-action@v1.12.0 + uses: crowdin/github-action@v1.19.0 with: config: crowdin.yml crowdin_branch_name: ${{ env.CROWDIN_BRANCH }} diff --git a/.github/workflows/nightly-pr.yml b/.github/workflows/nightly-pr.yml index a66ff7b5631e..b55f1652a72b 100644 --- a/.github/workflows/nightly-pr.yml +++ b/.github/workflows/nightly-pr.yml @@ -12,8 +12,8 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository_owner == 'vuetifyjs' }} steps: - - uses: actions/checkout@v2 - - uses: actions/github-script@v6 + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 with: script: | const pr = await github.rest.pulls.get({ @@ -32,8 +32,8 @@ jobs: release-id: pr-${{ github.event.inputs.pr }}.${{ env.SHORT_SHA }} npm-tag: pr npm-token: ${{ secrets.NPM_TOKEN }} - - uses: actions/checkout@v2 - - uses: actions/github-script@v6 + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 with: script: | const fullVersion = process.env.FULL_VERSION diff --git a/.github/workflows/nightly-schedule.yml b/.github/workflows/nightly-schedule.yml index 1a796988b79f..2429f338b08b 100644 --- a/.github/workflows/nightly-schedule.yml +++ b/.github/workflows/nightly-schedule.yml @@ -25,7 +25,7 @@ jobs: - branch: 'v2-dev' tag: 'v2-dev' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: ${{ matrix.branch }} fetch-depth: 0 @@ -45,14 +45,14 @@ jobs: release-id: ${{ matrix.branch }}.${{ env.RELEASE_ID }} npm-tag: ${{ matrix.tag }} npm-token: ${{ secrets.NPM_TOKEN }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 percy: name: Visual regression tests runs-on: ubuntu-latest if: ${{ github.repository_owner == 'vuetifyjs' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: ref: master fetch-depth: 0 @@ -73,7 +73,7 @@ jobs: PERCY_BRANCH: master PERCY_TARGET_BRANCH: master PERCY_COMMIT: ${{ env.COMMIT }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: failure() with: name: cypress-screenshots diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 78345fd0f66c..6b25c9fe022e 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -7,7 +7,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: vuetifyjs/triage-action@master with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/lerna.json b/lerna.json index d8a3a31e5d6a..d7e638719ca2 100644 --- a/lerna.json +++ b/lerna.json @@ -13,6 +13,6 @@ } }, "npmClient": "yarn", - "version": "3.5.9", + "version": "3.5.11", "useWorkspaces": true } diff --git a/packages/api-generator/package.json b/packages/api-generator/package.json index 5c60ea205b5a..b08a9bcc089b 100755 --- a/packages/api-generator/package.json +++ b/packages/api-generator/package.json @@ -1,6 +1,6 @@ { "name": "@vuetify/api-generator", - "version": "3.5.9", + "version": "3.5.11", "private": true, "description": "", "scripts": { @@ -17,7 +17,7 @@ "ts-morph": "^20.0.0", "tsx": "^4.6.2", "vue": "^3.4.19", - "vuetify": "^3.5.9" + "vuetify": "^3.5.11" }, "devDependencies": { "@types/stringify-object": "^4.0.5" diff --git a/packages/docs/auto-imports.d.ts b/packages/docs/auto-imports.d.ts index 3bc411259825..7e625ea903aa 100644 --- a/packages/docs/auto-imports.d.ts +++ b/packages/docs/auto-imports.d.ts @@ -75,6 +75,7 @@ declare global { const useOneStore: typeof import('@vuetify/one')['useOneStore'] const usePinsStore: typeof import('./src/stores/pins')['usePinsStore'] const usePlayground: typeof import('./src/composables/playground')['usePlayground'] + const useProductsStore: typeof import('@vuetify/one')['useProductsStore'] const usePromotionsStore: typeof import('./src/stores/promotions')['usePromotionsStore'] const useReleasesStore: typeof import('./src/stores/releases')['useReleasesStore'] const useRoute: typeof import('vue-router')['useRoute'] @@ -164,6 +165,7 @@ declare module 'vue' { readonly useOneStore: UnwrapRef<typeof import('@vuetify/one')['useOneStore']> readonly usePinsStore: UnwrapRef<typeof import('./src/stores/pins')['usePinsStore']> readonly usePlayground: UnwrapRef<typeof import('./src/composables/playground')['usePlayground']> + readonly useProductsStore: UnwrapRef<typeof import('@vuetify/one')['useProductsStore']> readonly usePromotionsStore: UnwrapRef<typeof import('./src/stores/promotions')['usePromotionsStore']> readonly useReleasesStore: UnwrapRef<typeof import('./src/stores/releases')['useReleasesStore']> readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']> @@ -252,6 +254,7 @@ declare module '@vue/runtime-core' { readonly useOneStore: UnwrapRef<typeof import('@vuetify/one')['useOneStore']> readonly usePinsStore: UnwrapRef<typeof import('./src/stores/pins')['usePinsStore']> readonly usePlayground: UnwrapRef<typeof import('./src/composables/playground')['usePlayground']> + readonly useProductsStore: UnwrapRef<typeof import('@vuetify/one')['useProductsStore']> readonly usePromotionsStore: UnwrapRef<typeof import('./src/stores/promotions')['usePromotionsStore']> readonly useReleasesStore: UnwrapRef<typeof import('./src/stores/releases')['useReleasesStore']> readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']> diff --git a/packages/docs/components.d.ts b/packages/docs/components.d.ts index ca7d4e752315..c53f1629ae37 100644 --- a/packages/docs/components.d.ts +++ b/packages/docs/components.d.ts @@ -40,6 +40,7 @@ declare module 'vue' { AppBarThemeToggle: typeof import('./src/components/app/bar/ThemeToggle.vue')['default'] AppBtn: typeof import('./src/components/app/Btn.vue')['default'] AppCaption: typeof import('./src/components/app/Caption.vue')['default'] + AppCommitBtn: typeof import('./src/components/app/CommitBtn.vue')['default'] AppDivider: typeof import('./src/components/app/Divider.vue')['default'] AppDrawerAppend: typeof import('./src/components/app/drawer/Append.vue')['default'] AppDrawerDrawer: typeof import('./src/components/app/drawer/Drawer.vue')['default'] @@ -86,6 +87,7 @@ declare module 'vue' { AppToc: typeof import('./src/components/app/Toc.vue')['default'] AppTooltipBtn: typeof import('./src/components/app/TooltipBtn.vue')['default'] AppV2Banner: typeof import('./src/components/app/V2Banner.vue')['default'] + AppVersionBtn: typeof import('./src/components/app/VersionBtn.vue')['default'] AppVerticalDivider: typeof import('./src/components/app/VerticalDivider.vue')['default'] Backmatter: typeof import('./src/components/Backmatter.vue')['default'] ComponentsListItem: typeof import('./src/components/components/ListItem.vue')['default'] @@ -97,6 +99,7 @@ declare module 'vue' { DocMadeWithVueAttribution: typeof import('./src/components/doc/MadeWithVueAttribution.vue')['default'] DocMadeWithVuetifyGallery: typeof import('./src/components/doc/MadeWithVuetifyGallery.vue')['default'] DocMadeWithVuetifyLink: typeof import('./src/components/doc/MadeWithVuetifyLink.vue')['default'] + DocPremiumThemesGallery: typeof import('./src/components/doc/PremiumThemesGallery.vue')['default'] DocReadyForMore: typeof import('./src/components/doc/ReadyForMore.vue')['default'] DocRelatedPage: typeof import('./src/components/doc/RelatedPage.vue')['default'] DocRelatedPages: typeof import('./src/components/doc/RelatedPages.vue')['default'] diff --git a/packages/docs/package.json b/packages/docs/package.json index 07d0b92fbd16..7b6a5370bb2e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -3,7 +3,7 @@ "description": "A Vue.js project", "private": true, "author": "John Leider <john@vuetifyjs.com>", - "version": "3.5.9", + "version": "3.5.11", "repository": { "type": "git", "url": "git+https://github.com/vuetifyjs/vuetify.git", @@ -23,7 +23,7 @@ "@cosmicjs/sdk": "^1.0.11", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", - "@vuetify/one": "^1.2.4", + "@vuetify/one": "^1.5.0", "algoliasearch": "^4.20.0", "fflate": "^0.8.1", "isomorphic-fetch": "^3.0.0", @@ -38,7 +38,7 @@ "vue-i18n": "^9.7.1", "vue-instantsearch": "^4.12.1", "vue-prism-component": "^2.0.0", - "vuetify": "^3.5.9" + "vuetify": "^3.5.11" }, "devDependencies": { "@emailjs/browser": "^3.11.0", @@ -50,7 +50,7 @@ "@vitejs/plugin-basic-ssl": "^1.0.2", "@vitejs/plugin-vue": "^4.5.2", "@vue/compiler-sfc": "^3.4.19", - "@vuetify/api-generator": "^3.5.9", + "@vuetify/api-generator": "^3.5.11", "ajv": "^8.12.0", "async-es": "^3.2.5", "date-fns": "^2.30.0", diff --git a/packages/docs/src/components/Backmatter.vue b/packages/docs/src/components/Backmatter.vue index ba43212da6eb..d0dd79f1eb87 100644 --- a/packages/docs/src/components/Backmatter.vue +++ b/packages/docs/src/components/Backmatter.vue @@ -11,8 +11,6 @@ <DocUpNext /> - <PromotedPromoted /> - <DocContribute /> </section> </template> diff --git a/packages/docs/src/components/app/CommitBtn.vue b/packages/docs/src/components/app/CommitBtn.vue new file mode 100644 index 000000000000..f5b18197d131 --- /dev/null +++ b/packages/docs/src/components/app/CommitBtn.vue @@ -0,0 +1,18 @@ +<template> + <v-btn + v-if="commits.latest" + :href="`https://github.com/vuetifyjs/vuetify/commit/${commits.latest?.sha}`" + :text="commits.latest?.sha.slice(0, 7)" + class="text-caption" + prepend-icon="mdi-source-commit" + rel="noopener noreferrer" + size="small" + target="_blank" + variant="text" + slim + /> +</template> + +<script lang="ts" setup> + const commits = useCommitsStore() +</script> diff --git a/packages/docs/src/components/app/VersionBtn.vue b/packages/docs/src/components/app/VersionBtn.vue new file mode 100644 index 000000000000..36ed352adc62 --- /dev/null +++ b/packages/docs/src/components/app/VersionBtn.vue @@ -0,0 +1,16 @@ +<template> + <v-btn + :text="version" + :to="rpath(`/getting-started/release-notes/?version=v${version}`)" + class="text-caption" + prepend-icon="mdi-tag-outline" + size="small" + variant="text" + slim + /> +</template> + +<script lang="ts" setup> + // Utilities + import { version } from 'vuetify' +</script> diff --git a/packages/docs/src/components/app/drawer/Append.vue b/packages/docs/src/components/app/drawer/Append.vue index 75cceb27367c..328c8e881b59 100644 --- a/packages/docs/src/components/app/drawer/Append.vue +++ b/packages/docs/src/components/app/drawer/Append.vue @@ -5,36 +5,13 @@ <AppDrawerDrawerToggleRail v-if="auth.isSubscriber" class="me-2" /> <div class="d-flex ms-auto overflow-hidden"> - <v-btn - v-if="commits.latest" - :href="`https://github.com/vuetifyjs/vuetify/commit/${commits.latest?.sha}`" - :text="commits.latest?.sha.slice(0, 7)" - class="text-caption me-2" - prepend-icon="mdi-source-commit" - rel="noopener noreferrer" - size="small" - target="_blank" - variant="text" - slim - /> + <AppCommitBtn class="me-2" /> - <v-btn - :text="version" - :to="rpath(`/getting-started/release-notes/?version=v${version}`)" - class="text-caption" - prepend-icon="mdi-tag-outline" - size="small" - variant="text" - slim - /> + <AppVersionBtn /> </div> </div> </template> <script setup lang="ts"> - // Utilities - import { version } from 'vuetify' - const auth = useAuthStore() - const commits = useCommitsStore() </script> diff --git a/packages/docs/src/components/doc/MadeWithVuetifyGallery.vue b/packages/docs/src/components/doc/MadeWithVuetifyGallery.vue index be75beb13996..2d5ad1563f5d 100644 --- a/packages/docs/src/components/doc/MadeWithVuetifyGallery.vue +++ b/packages/docs/src/components/doc/MadeWithVuetifyGallery.vue @@ -11,9 +11,9 @@ cols="12" md="4" > - <v-skeleton-loader height="180" /> + <v-skeleton-loader class="rounded-b-0" height="180" /> - <v-skeleton-loader type="text" /> + <v-skeleton-loader class="rounded-t-0" type="text" /> </v-col> </v-row> @@ -42,9 +42,10 @@ :name="project.raw.title" :src="project.raw.image" :title="project.raw.title" - class="border" - height="180" - min-height="180" + height="230" + max-height="230" + min-height="230" + cover eager /> </a> @@ -79,7 +80,7 @@ const store = useMadeWithVuetifyStore() const items = computed(() => { - return shuffle(store.items) + return shuffle(store.items.slice()) }) function shuffle (array) { diff --git a/packages/docs/src/components/doc/MadeWithVuetifyLink.vue b/packages/docs/src/components/doc/MadeWithVuetifyLink.vue index 8f6af981d5b5..111db0ebb378 100644 --- a/packages/docs/src/components/doc/MadeWithVuetifyLink.vue +++ b/packages/docs/src/components/doc/MadeWithVuetifyLink.vue @@ -3,6 +3,7 @@ :aria-label="t('see-more-projects')" :size="size" :to="rpath('/resources/made-with-vuetify/')" + append-icon="mdi-page-next" color="primary" variant="outlined" @click="onClick" diff --git a/packages/docs/src/components/doc/PremiumThemesGallery.vue b/packages/docs/src/components/doc/PremiumThemesGallery.vue new file mode 100644 index 000000000000..2964c122969e --- /dev/null +++ b/packages/docs/src/components/doc/PremiumThemesGallery.vue @@ -0,0 +1,90 @@ +<template> + <v-sheet + class="mx-auto text-center pa-3 mb-4" + color="transparent" + max-width="900" + > + <v-row v-if="!items.length"> + <v-col + v-for="n in 9" + :key="n" + cols="12" + md="4" + > + <v-skeleton-loader class="rounded-b-0" height="180" /> + + <v-skeleton-loader class="rounded-t-0" type="text" /> + </v-col> + </v-row> + + <v-data-iterator + v-else + :items="items" + :items-per-page="itemsPerPage" + :page="page" + > + <template #default="{ items: _items }"> + <v-row style="min-height: 750px;"> + <v-col + v-for="product in _items" + :key="product.raw.id" + cols="12" + sm="4" + > + <a + :href="`https://store.vuetifyjs.com/products/${product.raw.handle}`" + class="d-block text-decoration-none" + rel="noopener noreferrer" + style="min-height: 205px;" + target="_blank" + > + <DocThemeCard + :product="{ + title: product.raw.title, + src: product.raw.image, + }" + class="text-center" + /> + </a> + </v-col> + </v-row> + </template> + </v-data-iterator> + </v-sheet> + + <v-btn + append-icon="mdi-open-in-new" + aria-label="See More Templates" + color="primary" + href="https://store.vuetifyjs.com" + rel="noopener noreferrer" + size="large" + target="_blank" + variant="outlined" + > + <span class="text-capitalize font-weight-regular"> + See More Templates + </span> + </v-btn> +</template> + +<script setup> + defineProps({ + itemsPerPage: { + type: [Number, String], + default: 9, + }, + }) + + const page = ref(1) + + const products = useProductsStore() + + const items = shallowRef([]) + + onMounted(async () => { + await products.index() + + items.value = products.randomize(products.themes) + }) +</script> diff --git a/packages/docs/src/components/doc/Releases.vue b/packages/docs/src/components/doc/Releases.vue index 3093c9d98099..3709c08558b3 100644 --- a/packages/docs/src/components/doc/Releases.vue +++ b/packages/docs/src/components/doc/Releases.vue @@ -11,6 +11,8 @@ item-title="name" label="Select Release Version" prepend-inner-icon="mdi-text-box-search-outline" + rounded="b-0" + variant="solo-filled" hide-details hide-no-data persistent-placeholder @@ -73,7 +75,7 @@ > <div v-if="model?.author" - class="d-flex align-center justify-space-between pa-4 bg-grey-lighten-5" + class="d-flex align-center justify-space-between pa-4 bg-surface-light border-y" > <div class="d-flex align-center text-caption"> <i18n-t v-if="publishedOn" keypath="published" scope="global"> diff --git a/packages/docs/src/components/doc/ThemeCard.vue b/packages/docs/src/components/doc/ThemeCard.vue index 005d2b146bd6..fd91029b9200 100644 --- a/packages/docs/src/components/doc/ThemeCard.vue +++ b/packages/docs/src/components/doc/ThemeCard.vue @@ -14,7 +14,7 @@ min-height="230" cover > - <figcaption class="d-flex text-subtitle-2 align-center text-capitalize mt-3"> + <figcaption class="d-flex text-subtitle-2 align-center justify-center text-capitalize mt-3"> <span v-text="product.title" /> <v-chip @@ -27,7 +27,7 @@ /> <span - v-else + v-else-if="product.price" class="ms-auto text-subtitle-1 font-weight-bold" v-text="`$${product.price}`" /> diff --git a/packages/docs/src/components/examples/UsageExample.vue b/packages/docs/src/components/examples/UsageExample.vue index cd2a6d6debc6..a81c1ab9366c 100644 --- a/packages/docs/src/components/examples/UsageExample.vue +++ b/packages/docs/src/components/examples/UsageExample.vue @@ -164,7 +164,7 @@ { name: 'template', language: 'html', - content: `<template>\n <v-app>\n <v-container>\n ${props.code.replaceAll('\n', '\n ')}\n </v-container>\n </v-app>\n</template>`, + content: `<template>\n <v-app>\n <v-container>\n ${props.code.replaceAll('\n', '\n ')}\n </v-container>\n </v-app>\n</template>\n${props.script}`, }, ])) </script> diff --git a/packages/docs/src/components/home/Entry.vue b/packages/docs/src/components/home/Entry.vue index 60b17ce3b3c0..80e581613dd6 100644 --- a/packages/docs/src/components/home/Entry.vue +++ b/packages/docs/src/components/home/Entry.vue @@ -21,42 +21,74 @@ <br> - <v-hover> - <template #default="{ isHovering, props }"> + <v-row :justify="mdAndDown ? 'center' : undefined"> + <v-col cols="auto"> + <v-hover> + <template #default="{ isHovering, props }"> + <v-sheet + class="px-2 py-2 d-inline-flex align-center text-mono text-body-2 text-no-wrap" + color="surface" + width="215" + border + rounded + v-bind="props" + > + <v-icon + class="me-1" + color="medium-emphasis" + icon="mdi-chevron-right" + size="16" + /> + + {{ randomPackage }} create + + <span class="text-primary font-weight-medium ms-2"> + vuetify + </span> + + <v-icon + :icon="isCopying ? 'mdi-check' : 'mdi-clipboard-text-outline'" + :style="{ + opacity: isHovering || isCopying ? 1 : 0, + }" + class="ms-auto" + color="medium-emphasis" + size="17" + @click="copy" + /> + </v-sheet> + </template> + </v-hover> + </v-col> + + <v-col cols="auto"> <v-sheet - class="px-2 py-2 d-inline-flex align-center text-mono text-body-2 text-no-wrap" + class="pa-1 ps-3 d-inline-flex align-center justify-space-between text-caption" color="surface" width="215" border rounded - v-bind="props" > - <v-icon - class="me-1" - color="medium-emphasis" - icon="mdi-chevron-right" - size="16" - /> - - yarn create - - <span class="text-primary font-weight-medium ms-2"> - vuetify - </span> - - <v-icon - :icon="isCopying ? 'mdi-check' : 'mdi-clipboard-text-outline'" - :style="{ - opacity: isHovering || isCopying ? 1 : 0, - }" - class="ms-auto" - color="medium-emphasis" - size="17" - @click="copy" - /> + <span class="me-2">Latest Commit:</span> + + <AppCommitBtn /> </v-sheet> - </template> - </v-hover> + </v-col> + + <v-col cols="auto"> + <v-sheet + class="pa-1 ps-3 d-inline-flex align-center justify-space-between text-caption" + color="surface" + width="215" + border + rounded + > + <span class="me-2">Latest Release:</span> + + <AppVersionBtn /> + </v-sheet> + </v-col> + </v-row> </v-col> </v-row> </v-container> @@ -65,6 +97,10 @@ <script setup> const isCopying = shallowRef(false) + const { mdAndDown } = useDisplay() + const packages = ['npm', 'yarn', 'pnpm', 'bun'] + const randomPackage = packages[Math.floor(Math.random() * packages.length)] + function copy () { isCopying.value = true diff --git a/packages/docs/src/components/home/Footer.vue b/packages/docs/src/components/home/Footer.vue index 10256196ec97..6701cceb749a 100644 --- a/packages/docs/src/components/home/Footer.vue +++ b/packages/docs/src/components/home/Footer.vue @@ -2,16 +2,15 @@ <v-footer id="footer" class="d-block py-6" - color="surface-bright" + color="surface-light" > <v-container class="text-center"> <v-row> <v-col cols="12"> <v-img - :src="`https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-${theme.name.value}-slim.svg`" + :src="`https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-${theme.current.value.dark ? 'dark' : 'light'}-slim.svg`" class="mx-auto" height="64" - width="64" contain /> </v-col> diff --git a/packages/docs/src/components/home/Logo.vue b/packages/docs/src/components/home/Logo.vue index 6ce261ac14cc..d567d832d00e 100644 --- a/packages/docs/src/components/home/Logo.vue +++ b/packages/docs/src/components/home/Logo.vue @@ -17,6 +17,6 @@ const theme = useTheme() const logo = computed(() => { - return `vuetify-logo-${theme.name.value}-atom.svg` + return `vuetify-logo-${theme.current.value.dark ? 'dark' : 'light'}-atom.svg` }) </script> diff --git a/packages/docs/src/components/home/Sponsors.vue b/packages/docs/src/components/home/Sponsors.vue index 76aa251d6ce9..0b4c436211b4 100644 --- a/packages/docs/src/components/home/Sponsors.vue +++ b/packages/docs/src/components/home/Sponsors.vue @@ -30,7 +30,7 @@ <br> <br> - <SponsorLink size="large" /> + <SponsorLink append-icon="mdi-page-next" size="large" /> </v-sheet> </template> diff --git a/packages/docs/src/data/nav.json b/packages/docs/src/data/nav.json index 08d65dd87aa9..4c10ab2a81ca 100644 --- a/packages/docs/src/data/nav.json +++ b/packages/docs/src/data/nav.json @@ -231,6 +231,10 @@ "title": "floating-action-buttons", "subfolder": "components" }, + { + "title": "number-inputs", + "subfolder": "components" + }, { "title": "sparklines", "subfolder": "components" diff --git a/packages/docs/src/examples/v-data-iterator/usage.vue b/packages/docs/src/examples/v-data-iterator/usage.vue index d76504918d5b..14f157e581b7 100644 --- a/packages/docs/src/examples/v-data-iterator/usage.vue +++ b/packages/docs/src/examples/v-data-iterator/usage.vue @@ -71,14 +71,14 @@ }) const script = computed(() => { - return `export default { - data: () => ({ - page: 1, - items: Array.from({ length: 15 }, (k, v) => ({ - title: 'Item ' + v + 1, - text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima, at placeat totam, magni doloremque veniam neque porro libero rerum unde voluptatem!', - })), - }), -}` + return `<script setup> + import { ref } from 'vue' + + const page = ref(1) + const items = Array.from({ length: 15 }, (k, v) => ({ + title: 'Item ' + v + 1, + text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Commodi, ratione debitis quis est labore voluptatibus! Eaque cupiditate minima, at placeat totam, magni doloremque veniam neque porro libero rerum unde voluptatem!', + })) +<` + '/script>' }) </script> diff --git a/packages/docs/src/examples/v-data-table/usage.vue b/packages/docs/src/examples/v-data-table/usage.vue index 0d345af90d93..3a67557dbbcd 100644 --- a/packages/docs/src/examples/v-data-table/usage.vue +++ b/packages/docs/src/examples/v-data-table/usage.vue @@ -4,6 +4,7 @@ :code="code" :name="name" :options="options" + :script="script" > <div> <v-data-table @@ -104,6 +105,6 @@ }) const code = computed(() => { - return `<template>\n <v-data-table${propsToString(props.value, 2)}></v-data-table>\n</template>\n\n${script.value}` + return `<v-data-table${propsToString(props.value, 2)}></v-data-table>` }) </script> diff --git a/packages/docs/src/examples/v-infinite-scroll/usage.vue b/packages/docs/src/examples/v-infinite-scroll/usage.vue index d6256d067a1c..9265fa96f83f 100644 --- a/packages/docs/src/examples/v-infinite-scroll/usage.vue +++ b/packages/docs/src/examples/v-infinite-scroll/usage.vue @@ -57,7 +57,7 @@ return ` <template v-for="(item, index) in items" :key="item"> <div :class="['pa-2', index % 2 === 0 ? 'bg-grey-lighten-2' : '']"> - Item #{{ item }} + Item number #{{ item }} </div> </template> ` @@ -68,28 +68,26 @@ }) const script = computed(() => { - return `export default { - data: () => ({ - items: [], - }), + return `<script setup> + import { ref } from 'vue' - methods: { - async api () { - return new Promise(resolve => { - setTimeout(() => { - resolve(Array.from({ length: 10 }, (k, v) => v + this.items.at(-1) + 1)) - }, 1000) - }) - }, - async load ({ done }) { - // Perform API call - const res = await this.api() + const items = ref(Array.from({ length: 30 }, (k, v) => v + 1)) + + async function api () { + return new Promise(resolve => { + setTimeout(() => { + resolve(Array.from({ length: 10 }, (k, v) => v + items.value.at(-1) + 1)) + }, 1000) + }) + } + async function load ({ done }) { + // Perform API call + const res = await api() - this.items.push(...res) + items.value.push(...res) - done('ok') - }, - }, -}` + done('ok') + } +<` + '/script>' }) </script> diff --git a/packages/docs/src/examples/v-number-input/prop-control-variant.vue b/packages/docs/src/examples/v-number-input/prop-control-variant.vue new file mode 100644 index 000000000000..7f5c7d49e029 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-control-variant.vue @@ -0,0 +1,23 @@ +<template> + <v-container> + <v-row> + <v-col cols="12" md="4" sm="4"> + <h5>Default</h5> + + <v-number-input control-variant="default"></v-number-input> + </v-col> + + <v-col cols="12" md="4" sm="4"> + <h5>Stacked</h5> + + <v-number-input control-variant="stacked"></v-number-input> + </v-col> + + <v-col cols="12" md="4" sm="4"> + <h5>Split</h5> + + <v-number-input control-variant="split"></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/prop-hide-input.vue b/packages/docs/src/examples/v-number-input/prop-hide-input.vue new file mode 100644 index 000000000000..954ea1bc9b26 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-hide-input.vue @@ -0,0 +1,9 @@ +<template> + <v-container> + <v-row justify="center"> + <v-col cols="auto"> + <v-number-input hide-input></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/prop-inset.vue b/packages/docs/src/examples/v-number-input/prop-inset.vue new file mode 100644 index 000000000000..d40c6caecdd7 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-inset.vue @@ -0,0 +1,41 @@ +<template> + <v-container> + <v-row> + <v-col cols="12" sm="6"> + <h5>Default</h5> + + <v-number-input + control-variant="default" + inset + ></v-number-input> + </v-col> + + <v-col cols="12" sm="6"> + <h5>Stacked</h5> + + <v-number-input + control-variant="stacked" + inset + ></v-number-input> + </v-col> + + <v-col cols="12" sm="6"> + <h5>Split</h5> + + <v-number-input + control-variant="split" + inset + ></v-number-input> + </v-col> + + <v-col cols="12" sm="6"> + <h5>Hide-input</h5> + + <v-number-input + hide-input + inset + ></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/prop-min-max.vue b/packages/docs/src/examples/v-number-input/prop-min-max.vue new file mode 100644 index 000000000000..3b9d367b404c --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-min-max.vue @@ -0,0 +1,15 @@ +<template> + <v-container> + <v-row> + <v-col> + <h5>min:10/max:20</h5> + + <v-number-input + :max="20" + :min="10" + :model-value="15" + ></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/prop-reverse.vue b/packages/docs/src/examples/v-number-input/prop-reverse.vue new file mode 100644 index 000000000000..8319e2ec5258 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-reverse.vue @@ -0,0 +1,31 @@ +<template> + <v-container> + <v-row> + <v-col cols="12" md="4" sm="4"> + <h5>Default</h5> + + <v-number-input + control-variant="default" + reverse + ></v-number-input> + </v-col> + + <v-col cols="12" md="4" sm="4"> + <h5>Stacked</h5> + + <v-number-input + control-variant="stacked" + reverse + ></v-number-input> + </v-col> + + <v-col cols="12" md="4" sm="4"> + <h5>Split</h5> + + <v-number-input + control-variant="split" + ></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/prop-step.vue b/packages/docs/src/examples/v-number-input/prop-step.vue new file mode 100644 index 000000000000..0c0ef3d16b72 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/prop-step.vue @@ -0,0 +1,16 @@ +<template> + <v-container> + <v-row> + <v-col> + <h5>step 2; min:10; max:20</h5> + + <v-number-input + :max="20" + :min="10" + :model-value="15" + :step="2" + ></v-number-input> + </v-col> + </v-row> + </v-container> +</template> diff --git a/packages/docs/src/examples/v-number-input/usage.vue b/packages/docs/src/examples/v-number-input/usage.vue new file mode 100644 index 000000000000..1294a9a61889 --- /dev/null +++ b/packages/docs/src/examples/v-number-input/usage.vue @@ -0,0 +1,59 @@ +<template> + <ExamplesUsageExample + v-model="model" + :code="code" + :name="name" + :options="options" + > + <div class="text-center"> + <v-number-input v-bind="props"></v-number-input> + </div> + + <template v-slot:configuration> + <v-select + v-model="controlVariant" + :items="controlVariantOptions" + label="Control Variant" + ></v-select> + <v-checkbox v-model="reverse" label="Reverse"></v-checkbox> + <v-checkbox v-model="inset" label="Inset"></v-checkbox> + <v-checkbox v-model="hideInput" label="HideInput"></v-checkbox> + <v-text-field v-model="label" label="Label" clearable></v-text-field> + </template> + </ExamplesUsageExample> +</template> + +<script setup> + const name = ref('v-number-input') + const model = ref('default') + const options = ['outlined', 'filled', 'solo', 'solo-inverted', 'solo-filled'] + const controlVariantOptions = ['default', 'stacked', 'split'] + const reverse = ref(false) + const controlVariant = ref('default') + const disabled = ref(false) + const loading = ref(false) + const inset = ref(false) + const hideInput = ref(false) + const label = ref('') + + const props = computed(() => { + return { + reverse: reverse.value, + controlVariant: controlVariant.value, + disabled: disabled.value || undefined, + label: label.value, + loading: loading.value || undefined, + hideInput: hideInput.value, + inset: inset.value, + variant: model.value !== 'default' ? model.value : undefined, + } + }) + + const slots = computed(() => { + return `` + }) + + const code = computed(() => { + return `<v-number-input${propsToString(props.value)}>${slots.value}</v-number-input>` + }) +</script> diff --git a/packages/docs/src/pages/en/components/all.md b/packages/docs/src/pages/en/components/all.md index 8d21f6c2aeb3..9cdfa4ffabec 100644 --- a/packages/docs/src/pages/en/components/all.md +++ b/packages/docs/src/pages/en/components/all.md @@ -232,6 +232,12 @@ Form components are used to collect user input in a variety of ways. </ComponentsListItem> +<ComponentsListItem name="Number input component" src="number-inputs" labs> + + The number input component is used for collecting numerical data from the user + +</ComponentsListItem> + <ComponentsListItem name="OTP Input component" src="otp-input" > The OTP input component is used for MFA authentication via input field diff --git a/packages/docs/src/pages/en/components/number-inputs.md b/packages/docs/src/pages/en/components/number-inputs.md new file mode 100644 index 000000000000..74e0ea7437fb --- /dev/null +++ b/packages/docs/src/pages/en/components/number-inputs.md @@ -0,0 +1,101 @@ +--- +emphasized: true +meta: + title: Number inputs + description: The Number input component is used for ... + keywords: Number, vuetify number input component, vue number component +related: + - /components/inputs/ + - /components/text-fields/ + - /components/forms/ +features: + label: 'C: VNumberInput' + github: /components/VNumberInput/ + report: true +--- + +# Number inputs + +The VNumberInput extends the standard HTML number-type input, ensuring style consistency across browsers as a replacement for `<input type="number">` + +<page-features /> + +::: warning + +This feature requires [v3.5.10](/getting-started/release-notes/?version=v3.5.10) + +::: + +## Installation + +Labs components require a manual import and installation of the component. + +```js { resource="src/plugins/vuetify.js" } +import { VNumberInput } from 'vuetify/labs/VNumberInput' + +export default createVuetify({ + components: { + VNumberInput, + }, +}) +``` + +## Usage + +Here we display a list of settings that could be applied within an application. + +<ExamplesUsage name="v-number-input" /> + +<PromotedEntry /> + +## API + +| Component | Description | +| - | - | +| [v-number-input](/api/v-number-input/) | Primary Component | + +<ApiInline hide-links /> + +## Guide + +The `v-number-input` component is built upon the `v-field` and `v-input` components. It is used as a replacement for `<input type="number">`, accepting numeric values from the user. + +### Props + +The `v-number-input` component has support for most of `v-field`'s props and is follows the same design patterns as other inputs. + +#### Control-variant + +The `control-variant` prop offers an easy way to customize steppers button layout. The following values are valid options: **default**, **stacked** and **split**. + +<ExamplesExample file="v-number-input/prop-control-variant" /> + +#### Reverse + +The `reverse` prop automatically changes the stepper buttons' position to the opposite side for both the default and stacked control variants. + +<ExamplesExample file="v-number-input/prop-reverse" /> + +#### Hide-input + +The `hide-input` prop hides the input field, allowing only the stepper buttons to be visible. These stepper buttons follow a stacked control-variant layout. + +<ExamplesExample file="v-number-input/prop-hide-input" /> + +#### Inset + +The `inset` prop adjusts the style of the stepper buttons by reducing the size of the button dividers. + +<ExamplesExample file="v-number-input/prop-inset" /> + +#### Min/Max + +The `min` and `max` props specify the minimum and maximum values accepted by v-number-input, behaving identically to the native min and max attributes for `<input type="number">`. + +<ExamplesExample file="v-number-input/prop-min-max" /> + +#### Step + +The `step` prop behaves the same as the `step` attribute in the `<input type="number">`, it defines the incremental steps for adjusting the numeric value. + +<ExamplesExample file="v-number-input/prop-step" /> 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 f155575f58d1..f66e895086a8 100644 --- a/packages/docs/src/pages/en/features/display-and-platform.md +++ b/packages/docs/src/pages/en/features/display-and-platform.md @@ -56,7 +56,7 @@ If you are still using the Options API, you can access the display information o | - | - | | [useDisplay](/api/use-display/) | Composable | -# Breakpoints and Thresholds +## Breakpoints and Thresholds Threshold values generate the ranges used for various breakpoints seen throughout vuetify and the `useDisplay` composable. The system uses an "and up" mentality starting from `xs` at 0px. The default threshold values are displayed below. diff --git a/packages/docs/src/pages/en/getting-started/browser-support.md b/packages/docs/src/pages/en/getting-started/browser-support.md index 416cff323b12..430758fa9642 100644 --- a/packages/docs/src/pages/en/getting-started/browser-support.md +++ b/packages/docs/src/pages/en/getting-started/browser-support.md @@ -15,7 +15,7 @@ Vuetify 3 is a next generation framework that takes advantage of the latest web <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardVuetify /> ## Browsers diff --git a/packages/docs/src/pages/en/getting-started/contributing.md b/packages/docs/src/pages/en/getting-started/contributing.md index 079efaac4347..e5b542f59f6a 100644 --- a/packages/docs/src/pages/en/getting-started/contributing.md +++ b/packages/docs/src/pages/en/getting-started/contributing.md @@ -15,7 +15,7 @@ Vuetify is made possible by an amazing community that submits issues, creates pu <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardVuetify /> It is our job to enable you to create amazing applications. A lot of the time, you come across something that can be made better. Maybe you find a bug, or you have an idea for additional functionality. That's great! It's as easy as cloning the Vuetify repository to get started working in a development environment. diff --git a/packages/docs/src/pages/en/getting-started/frequently-asked-questions.md b/packages/docs/src/pages/en/getting-started/frequently-asked-questions.md index fb8dc7a3702e..6e8ea461048c 100644 --- a/packages/docs/src/pages/en/getting-started/frequently-asked-questions.md +++ b/packages/docs/src/pages/en/getting-started/frequently-asked-questions.md @@ -15,7 +15,7 @@ Stuck on a particular problem? Check some of these common gotchas before creatin <PageFeatures /> -<PromotedPromoted slug="discord-subscriber-help" /> +<VoPromotionsCardHighlight slug="vuetify-discord-subscriber-help" /> ## Questions diff --git a/packages/docs/src/pages/en/getting-started/installation.md b/packages/docs/src/pages/en/getting-started/installation.md index 782312315ae1..a0460ebb1d22 100644 --- a/packages/docs/src/pages/en/getting-started/installation.md +++ b/packages/docs/src/pages/en/getting-started/installation.md @@ -20,7 +20,7 @@ Get started with Vuetify, the world’s most popular Vue.js framework for buildi <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardHighlight slug="vuemastery-getting-started" /> ## Installation diff --git a/packages/docs/src/pages/en/getting-started/release-notes.md b/packages/docs/src/pages/en/getting-started/release-notes.md index ed0e5796e706..1c2c378ea737 100644 --- a/packages/docs/src/pages/en/getting-started/release-notes.md +++ b/packages/docs/src/pages/en/getting-started/release-notes.md @@ -16,6 +16,6 @@ The Vuetify team performs releases on a weekly basis. <PageFeatures /> -<DocReleases /> +<VoPromotionsCardVuetify /> -<PromotedEntry /> +<DocReleases /> diff --git a/packages/docs/src/pages/en/getting-started/unit-testing.md b/packages/docs/src/pages/en/getting-started/unit-testing.md index 8b261843f454..56626b030064 100644 --- a/packages/docs/src/pages/en/getting-started/unit-testing.md +++ b/packages/docs/src/pages/en/getting-started/unit-testing.md @@ -15,7 +15,7 @@ Add regression protection by adding unit tests to your Vuetify application <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardVuetify /> ## Usage 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 f2efef7459e5..d6b94603e30c 100644 --- a/packages/docs/src/pages/en/getting-started/upgrade-guide.md +++ b/packages/docs/src/pages/en/getting-started/upgrade-guide.md @@ -21,8 +21,6 @@ This page contains a detailed list of breaking changes and the steps required to <span class="text-h6">Many of the changes on this page can be applied automatically using [eslint-plugin-vuetify](https://www.npmjs.com/package/eslint-plugin-vuetify/)</span> ::: -<PromotedEntry /> - ::: info Before upgrading, make sure to consult the Official [Vue 3 Migration Guide](https://v3-migration.vuejs.org/) diff --git a/packages/docs/src/pages/en/getting-started/wireframes.md b/packages/docs/src/pages/en/getting-started/wireframes.md index 5400663dbaa0..09c5fd35c35d 100644 --- a/packages/docs/src/pages/en/getting-started/wireframes.md +++ b/packages/docs/src/pages/en/getting-started/wireframes.md @@ -15,7 +15,7 @@ The Vuetify **layout system** makes it easy to rapidly scaffold an application's <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardVuetify /> ## Examples diff --git a/packages/docs/src/pages/en/index.md b/packages/docs/src/pages/en/index.md index e3397693484a..50c5d7f92af3 100644 --- a/packages/docs/src/pages/en/index.md +++ b/packages/docs/src/pages/en/index.md @@ -24,17 +24,21 @@ The continued development and maintenance of Vuetify is made possible by these g <HomeSponsors /> -<br> +<v-divider style="max-width: 500px;" class="mx-auto my-16" /> -<v-divider style="max-width: 500px;" class="mx-auto" /> +## Templates Built With Vuetify -<br> +Check out these premium templates built using Vuetify.{style="max-width: 568px" .mx-auto .px-4} + +<DocPremiumThemesGallery /> + +<v-divider style="max-width: 500px;" class="mx-auto my-16" /> ## Made With Vuetify Check out these beautiful apps, plugins, and themes built using Vuetify.{style="max-width: 568px" .mx-auto .px-4} -<DocMadeWithVuetifyGallery class="pa-3" /> +<DocMadeWithVuetifyGallery class="pa-3 mb-4" /> <DocMadeWithVuetifyLink /> diff --git a/packages/docs/src/pages/en/introduction/why-vuetify.md b/packages/docs/src/pages/en/introduction/why-vuetify.md index 3665bdb157ba..048577a79810 100644 --- a/packages/docs/src/pages/en/introduction/why-vuetify.md +++ b/packages/docs/src/pages/en/introduction/why-vuetify.md @@ -16,7 +16,7 @@ Learn more about what Vuetify is, how to create an application from scratch, bro <PageFeatures /> -<PromotedEntry /> +<VoPromotionsCardVuetify /> ## What is Vuetify? @@ -27,6 +27,8 @@ Since its initial release in 2014, [Vue.js](https://vuejs.org/) has grown to be ## Getting started +<VoPromotionsCardHighlight slug="vuemastery-getting-started" class="mb-4" /> + The fastest way to try Vuetify is in the browser at [Vuetify Play](https://play.vuetifyjs.com/). For a complete list of installation options please navigate to the [Installation page](/getting-started/installation/). ## Why Vuetify? { id="why-vuetify" } diff --git a/packages/docs/src/pages/en/labs/introduction.md b/packages/docs/src/pages/en/labs/introduction.md index f375816514b1..408a46235971 100644 --- a/packages/docs/src/pages/en/labs/introduction.md +++ b/packages/docs/src/pages/en/labs/introduction.md @@ -79,6 +79,7 @@ The following is a list of available and up-and-coming components for use with L | [v-calendar](/components/calendars/) | A calendar component | [v3.4.9](/getting-started/release-notes/?version=v3.4.9) | | [v-empty-state](/components/empty-states/) | A component for displaying empty states | [v3.5.7](/getting-started/release-notes/?version=v3.5.7) | | [v-fab](/components/floating-action-buttons/) | A layout aware [v-btn](/components/buttons/) | [v3.5.7](/getting-started/release-notes/?version=v3.5.7) | +| [v-number-input](/components/number-input/) | A component for numerical data | [v3.5.10](/getting-started/release-notes/?version=v3.5.10) | | [v-speed-dial](/components/speed-dials/) | A component for display actions | [v3.5.8](/getting-started/release-notes/?version=v3.5.8) | | [v-sparkline](/components/sparklines/) | A basic data display component | [v3.5.5](/getting-started/release-notes/?version=v3.5.5) | | [v-treeview](/components/treeview/) | A treeview component | [v3.5.9](/getting-started/release-notes/?version=v3.5.9) | diff --git a/packages/docs/src/plugins/global-components.ts b/packages/docs/src/plugins/global-components.ts index 1f1a9853c033..0f40caa98f45 100644 --- a/packages/docs/src/plugins/global-components.ts +++ b/packages/docs/src/plugins/global-components.ts @@ -22,6 +22,7 @@ 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' @@ -73,6 +74,7 @@ export function installGlobalComponents (app: App) { .component('DocMadeWithVueAttribution', DocMadeWithVueAttribution) .component('DocMadeWithVuetifyGallery', DocMadeWithVuetifyGallery) .component('DocMadeWithVuetifyLink', DocMadeWithVuetifyLink) + .component('DocPremiumThemesGallery', DocPremiumThemesGallery) .component('DocReleases', DocReleases) .component('DocThemeVendor', DocThemeVendor) .component('ExamplesExample', ExamplesExample) diff --git a/packages/docs/src/service-worker.js b/packages/docs/src/service-worker.js index 87ccd5dcbe9f..4f2c7f5761ed 100644 --- a/packages/docs/src/service-worker.js +++ b/packages/docs/src/service-worker.js @@ -53,6 +53,8 @@ self.addEventListener('activate', event => { self.addEventListener('fetch', event => { const url = new URL(event.request.url, location.href) + if (!['http:', 'https:'].includes(url.protocol)) return + if (event.request.method !== 'GET') return if ( @@ -102,24 +104,31 @@ function getCacheKeyForUrl (url) { async function networkFirst (request) { const cache = await openCache('runtime') + const fromNetwork = fetch(request).then(response => ensureCacheableResponse(response)).catch(() => null) + const race = Promise.race([ + fromNetwork, + new Promise(resolve => setTimeout(() => resolve('timeout'), 3000)), + ]) - let response try { - response = ensureCacheableResponse(await fetch(request)) + const response = await race + if (response === 'timeout' || !response) { + const cached = await caches.match(request) + if (cached) return cached + throw new Error('Network timeout and no cache available') + } + const is400 = response?.status >= 400 && response?.status < 500 + if (response?.status === 200) { + cache.put(request, response.clone()) + } else if (!is400) { + await cache.delete(request) + } + return response } catch (e) { console.warn('[SW] Failed to fetch', e) - } - const is400 = response?.status >= 400 && response?.status < 500 - if (response?.status === 200) { - cache.put(request, response.clone()) - } else if (!is400) { const cached = await caches.match(request) - if (cached) return cached - } else { - await cache.delete(request) + return cached || Response.error() } - - return response } async function getFallbackDocument () { @@ -199,7 +208,7 @@ function createCacheKey (entry) { return { cacheKey: cacheKeyUrl.href, - url: new URL(url, location.href).href, + url: cacheKeyUrl.href, } } diff --git a/packages/docs/vite.config.mts b/packages/docs/vite.config.mts index d7508d54a0cf..2205f6b7b3fa 100644 --- a/packages/docs/vite.config.mts +++ b/packages/docs/vite.config.mts @@ -79,7 +79,15 @@ export default defineConfig(({ command, mode, ssrBuild }) => { ], imports: [ { - '@vuetify/one': ['createOne', 'useAuthStore', 'useHttpStore', 'useOneStore', 'useUserStore', 'useSettingsStore'], + '@vuetify/one': [ + 'createOne', + 'useAuthStore', + 'useHttpStore', + 'useOneStore', + 'useUserStore', + 'useSettingsStore', + 'useProductsStore', + ], 'lodash-es': ['camelCase', 'kebabCase', 'upperFirst'], pinia: ['defineStore', 'storeToRefs'], vue: [ diff --git a/packages/vuetify/package.json b/packages/vuetify/package.json index 2ef697b69013..f5048d952a38 100755 --- a/packages/vuetify/package.json +++ b/packages/vuetify/package.json @@ -1,7 +1,7 @@ { "name": "vuetify", "description": "Vue Material Component Framework", - "version": "3.5.9", + "version": "3.5.11", "author": { "name": "John Leider", "email": "john@vuetifyjs.com" @@ -173,10 +173,10 @@ }, "peerDependencies": { "typescript": ">=4.7", - "vite-plugin-vuetify": ">=1.0.0-alpha.12", + "vite-plugin-vuetify": ">=1.0.0", "vue": "^3.3.0", "vue-i18n": "^9.0.0", - "webpack-plugin-vuetify": ">=2.0.0-alpha.11" + "webpack-plugin-vuetify": ">=2.0.0" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/vuetify/src/components/VBreadcrumbs/VBreadcrumbs.tsx b/packages/vuetify/src/components/VBreadcrumbs/VBreadcrumbs.tsx index 5ed814505e75..c9af34e5c013 100644 --- a/packages/vuetify/src/components/VBreadcrumbs/VBreadcrumbs.tsx +++ b/packages/vuetify/src/components/VBreadcrumbs/VBreadcrumbs.tsx @@ -25,10 +25,12 @@ import type { PropType } from 'vue' import type { LinkProps } from '@/composables/router' import type { GenericProps } from '@/util' -export type BreadcrumbItem = string | (Partial<LinkProps> & { +export type InternalBreadcrumbItem = Partial<LinkProps> & { title: string disabled?: boolean -}) +} + +export type BreadcrumbItem = string | InternalBreadcrumbItem export const makeVBreadcrumbsProps = propsFactory({ activeClass: String, @@ -58,9 +60,9 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>( }, slots: { prepend: never - title: { item: T, index: number } + title: { item: InternalBreadcrumbItem, index: number } divider: { item: T, index: number } - item: { item: T, index: number } + item: { item: InternalBreadcrumbItem, index: number } default: never } ) => GenericProps<typeof props, typeof slots>>()({ diff --git a/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx b/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx index 03c31f44a2ca..d29d8893202e 100644 --- a/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx +++ b/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx @@ -55,6 +55,13 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({ const rangeStart = shallowRef() const rangeStop = shallowRef() + if (props.multiple === 'range' && model.value.length > 0) { + rangeStart.value = model.value[0] + if (model.value.length > 1) { + rangeStop.value = model.value[model.value.length - 1] + } + } + const atMax = computed(() => { const max = ['number', 'string'].includes(typeof props.multiple) ? Number(props.multiple) : Infinity @@ -68,15 +75,15 @@ export const VDatePickerMonth = genericComponent<VDatePickerMonthSlots>()({ rangeStart.value = _value model.value = [rangeStart.value] } else if (!rangeStop.value) { - if (adapter.isSameDay(value, rangeStart.value)) { + if (adapter.isSameDay(_value, rangeStart.value)) { rangeStart.value = undefined model.value = [] return - } else if (adapter.isBefore(value, rangeStart.value)) { - rangeStop.value = rangeStart.value + } else if (adapter.isBefore(_value, rangeStart.value)) { + rangeStop.value = adapter.endOfDay(rangeStart.value) rangeStart.value = _value } else { - rangeStop.value = _value + rangeStop.value = adapter.endOfDay(_value) } const diff = adapter.getDiff(rangeStop.value, rangeStart.value, 'days') diff --git a/packages/vuetify/src/components/VDatePicker/__tests__/VDatePicker.spec.cy.tsx b/packages/vuetify/src/components/VDatePicker/__tests__/VDatePicker.spec.cy.tsx index 7b68ddc38469..9d328f6133dc 100644 --- a/packages/vuetify/src/components/VDatePicker/__tests__/VDatePicker.spec.cy.tsx +++ b/packages/vuetify/src/components/VDatePicker/__tests__/VDatePicker.spec.cy.tsx @@ -21,4 +21,23 @@ describe('VDatePicker', () => { expect(model.value).to.have.length(11) }) }) + + it('selects a range of dates across month boundary', () => { + const model = ref<unknown[]>([]) + cy.mount(() => ( + <Application> + <VDatePicker v-model={ model.value } multiple="range" /> + </Application> + )) + + cy.get('.v-date-picker-controls__month-btn').click() + cy.get('.v-date-picker-months__content').contains('Jan').click() + cy.get('.v-date-picker-month__day').contains(7).click() + cy.get('.v-date-picker-controls__month-btn').click() + cy.get('.v-date-picker-months__content').contains('Feb').click() + cy.get('.v-date-picker-month__day').contains(7).click() + .then(() => { + expect(model.value).to.have.length(32) + }) + }) }) diff --git a/packages/vuetify/src/components/VField/VField.sass b/packages/vuetify/src/components/VField/VField.sass index b5800fe10c8f..668f57085507 100644 --- a/packages/vuetify/src/components/VField/VField.sass +++ b/packages/vuetify/src/components/VField/VField.sass @@ -453,6 +453,8 @@ .v-field--variant-outlined & top: calc(100% - 3px) + width: calc(100% - #{$field-border-width} * 2) + left: $field-border-width /* endregion */ /* region OVERLAY */ diff --git a/packages/vuetify/src/components/VField/VField.tsx b/packages/vuetify/src/components/VField/VField.tsx index 9a64ada45229..265aff40c071 100644 --- a/packages/vuetify/src/components/VField/VField.tsx +++ b/packages/vuetify/src/components/VField/VField.tsx @@ -212,7 +212,7 @@ export const VField = genericComponent<new <T>( useRender(() => { const isOutlined = props.variant === 'outlined' - const hasPrepend = (slots['prepend-inner'] || props.prependInnerIcon) + const hasPrepend = !!(slots['prepend-inner'] || props.prependInnerIcon) const hasClear = !!(props.clearable || slots.clear) const hasAppend = !!(slots['append-inner'] || props.appendInnerIcon || hasClear) const label = () => ( diff --git a/packages/vuetify/src/components/VIcon/VIcon.sass b/packages/vuetify/src/components/VIcon/VIcon.sass index 2da9b7376201..1585cf46fcf4 100644 --- a/packages/vuetify/src/components/VIcon/VIcon.sass +++ b/packages/vuetify/src/components/VIcon/VIcon.sass @@ -22,6 +22,10 @@ &--clickable cursor: pointer + &--disabled + pointer-events: none + opacity: $icon-disabled-opacity + @each $name in settings.$sizes &--size-#{$name} font-size: calc(var(--v-icon-size-multiplier) * #{map.get($icon-sizes, $name)}) diff --git a/packages/vuetify/src/components/VIcon/VIcon.tsx b/packages/vuetify/src/components/VIcon/VIcon.tsx index bd9a372eff39..4167c703d2a4 100644 --- a/packages/vuetify/src/components/VIcon/VIcon.tsx +++ b/packages/vuetify/src/components/VIcon/VIcon.tsx @@ -15,6 +15,7 @@ import { convertToUnit, flattenFragments, genericComponent, propsFactory, useRen export const makeVIconProps = propsFactory({ color: String, + disabled: Boolean, start: Boolean, end: Boolean, icon: IconValue, @@ -45,6 +46,7 @@ export const VIcon = genericComponent()({ node.type === Text && node.children && typeof node.children === 'string' )[0]?.children as string } + const hasClick = !!(attrs.onClick || attrs.onClickOnce) return ( <iconData.value.component @@ -57,7 +59,8 @@ export const VIcon = genericComponent()({ sizeClasses.value, textColorClasses.value, { - 'v-icon--clickable': !!attrs.onClick, + 'v-icon--clickable': hasClick, + 'v-icon--disabled': props.disabled, 'v-icon--start': props.start, 'v-icon--end': props.end, }, @@ -72,8 +75,9 @@ export const VIcon = genericComponent()({ textColorStyles.value, props.style, ]} - role={ attrs.onClick ? 'button' : undefined } - aria-hidden={ !attrs.onClick } + role={ hasClick ? 'button' : undefined } + aria-hidden={ !hasClick } + tabindex={ hasClick ? props.disabled ? -1 : 0 : undefined } > { slotValue } </iconData.value.component> diff --git a/packages/vuetify/src/components/VIcon/_variables.scss b/packages/vuetify/src/components/VIcon/_variables.scss index b376aa18e543..8d21b4f58437 100644 --- a/packages/vuetify/src/components/VIcon/_variables.scss +++ b/packages/vuetify/src/components/VIcon/_variables.scss @@ -3,6 +3,7 @@ @use '../../styles/tools'; // VIcon +$icon-disabled-opacity: 0.38 !default; $icon-left-margin-left: map.get(settings.$grid-gutters, 'md') !default; $icon-letter-spacing: normal !default; $icon-line-height: 1 !default; diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 54cf2ab1fc53..a273e4fa5682 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -178,9 +178,9 @@ export const VListItem = genericComponent<VListItemSlots>()({ link.navigate?.(e) - if (root.activatable) { + if (root.activatable.value) { activate(!isActivated.value, e) - } else if (root.selectable) { + } else if (root.selectable.value) { select(!isSelected.value, e) } else if (props.value != null) { select(!isSelected.value, e) diff --git a/packages/vuetify/src/components/VMenu/VMenu.tsx b/packages/vuetify/src/components/VMenu/VMenu.tsx index 08bebfa5458f..74f643e20157 100644 --- a/packages/vuetify/src/components/VMenu/VMenu.tsx +++ b/packages/vuetify/src/components/VMenu/VMenu.tsx @@ -135,6 +135,9 @@ export const VMenu = genericComponent<OverlaySlots>()({ isActive.value = false overlay.value?.activatorEl?.focus() } + } else if (['Enter', ' '].includes(e.key) && props.closeOnContentClick) { + isActive.value = false + parent?.closeParents() } } diff --git a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.sass b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.sass index 839adea358ee..7a9bacbcf5e0 100644 --- a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.sass +++ b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.sass @@ -70,7 +70,8 @@ width: 100% z-index: -1 - img + // TODO: remove in v4 + img:not(.v-img__img) height: $navigation-drawer-img-height object-fit: $navigation-drawer-img-object-fit width: $navigation-drawer-img-width diff --git a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx index 9cbde45a8b0b..346e7a4668c8 100644 --- a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx +++ b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx @@ -1,6 +1,10 @@ // Styles import './VNavigationDrawer.sass' +// Components +import { VDefaultsProvider } from '@/components/VDefaultsProvider' +import { VImg } from '@/components/VImg' + // Composables import { useSticky } from './sticky' import { useTouch } from './touch' @@ -249,10 +253,29 @@ export const VNavigationDrawer = genericComponent<VNavigationDrawerSlots>()({ > { hasImage && ( <div key="image" class="v-navigation-drawer__img"> - { slots.image - ? slots.image?.({ image: props.image }) - : (<img src={ props.image } alt="" />) - } + { !slots.image ? ( + <VImg + key="image-img" + alt="" + cover + height="inherit" + src={ props.image } + /> + ) : ( + <VDefaultsProvider + key="image-defaults" + disabled={ !props.image } + defaults={{ + VImg: { + alt: '', + cover: true, + height: 'inherit', + src: props.image, + }, + }} + v-slots:default={ slots.image } + /> + )} </div> )} diff --git a/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx b/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx index 27ab3bec289d..181e41c1392a 100644 --- a/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx +++ b/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx @@ -100,10 +100,11 @@ export const VOtpInput = genericComponent<VOtpInputSlots>()({ function onInput () { // The maxlength attribute doesn't work for the number type input, so the text type is used. // The following logic simulates the behavior of a number input. - if (props.type === 'number' && /[^0-9]/g.test(current.value.value)) { + if (isValidNumber(current.value.value)) { current.value.value = '' return } + const array = model.value.slice() const value = current.value.value @@ -165,7 +166,11 @@ export const VOtpInput = genericComponent<VOtpInputSlots>()({ e.preventDefault() e.stopPropagation() - model.value = (e?.clipboardData?.getData('Text') ?? '').split('') + const clipboardText = e?.clipboardData?.getData('Text') ?? '' + + if (isValidNumber(clipboardText)) return + + model.value = clipboardText.split('') inputRef.value?.[index].blur() } @@ -186,6 +191,10 @@ export const VOtpInput = genericComponent<VOtpInputSlots>()({ focusIndex.value = -1 } + function isValidNumber (value: string) { + return props.type === 'number' && /[^0-9]/g.test(value) + } + provideDefaults({ VField: { color: computed(() => props.color), diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index 5acfa5dbf9f6..dd257f4aadbe 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -162,6 +162,7 @@ export const VOverlay = genericComponent<OverlaySlots>()({ }) const root = ref<HTMLElement>() + const scrimEl = ref<HTMLElement>() const contentEl = ref<HTMLElement>() const { contentStyles, updateLocation } = useLocationStrategies(props, { isRtl, @@ -184,8 +185,11 @@ export const VOverlay = genericComponent<OverlaySlots>()({ else animateClick() } - function closeConditional () { - return isActive.value && globalTop.value + function closeConditional (e: Event) { + return isActive.value && globalTop.value && ( + // If using scrim, only close if clicking on it rather than anything opened on top + !props.scrim || e.target === scrimEl.value + ) } IN_BROWSER && watch(isActive, val => { @@ -297,6 +301,7 @@ export const VOverlay = genericComponent<OverlaySlots>()({ <Scrim color={ scrimColor } modelValue={ isActive.value && !!props.scrim } + ref={ scrimEl } { ...scrimEvents.value } /> <MaybeTransition @@ -332,6 +337,7 @@ export const VOverlay = genericComponent<OverlaySlots>()({ return { activatorEl, + scrimEl, target, animateClick, contentEl, diff --git a/packages/vuetify/src/components/VProgressLinear/VProgressLinear.sass b/packages/vuetify/src/components/VProgressLinear/VProgressLinear.sass index b9e2cfc7b330..50d30b774c62 100644 --- a/packages/vuetify/src/components/VProgressLinear/VProgressLinear.sass +++ b/packages/vuetify/src/components/VProgressLinear/VProgressLinear.sass @@ -57,7 +57,6 @@ right: auto top: 0 width: auto - will-change: left, right .long animation-name: indeterminate-ltr diff --git a/packages/vuetify/src/composables/date/adapters/vuetify.ts b/packages/vuetify/src/composables/date/adapters/vuetify.ts index 8dd97f14a99e..adbc3a0e664d 100644 --- a/packages/vuetify/src/composables/date/adapters/vuetify.ts +++ b/packages/vuetify/src/composables/date/adapters/vuetify.ts @@ -479,7 +479,7 @@ function setYear (date: Date, year: number) { } function startOfDay (date: Date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()) + return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0) } function endOfDay (date: Date) { diff --git a/packages/vuetify/src/composables/form.ts b/packages/vuetify/src/composables/form.ts index e90bd0e2d7b2..911a04f2380e 100644 --- a/packages/vuetify/src/composables/form.ts +++ b/packages/vuetify/src/composables/form.ts @@ -14,8 +14,8 @@ export interface FormProvide { register: (item: { id: number | string validate: () => Promise<string[]> - reset: () => void - resetValidation: () => void + reset: () => Promise<void> + resetValidation: () => Promise<void> }) => void unregister: (id: number | string) => void update: (id: number | string, isValid: boolean | null, errorMessages: string[]) => void @@ -30,8 +30,8 @@ export interface FormProvide { export interface FormField { id: number | string validate: () => Promise<string[]> - reset: () => void - resetValidation: () => void + reset: () => Promise<void> + resetValidation: () => Promise<void> isValid: boolean | null errorMessages: string[] } diff --git a/packages/vuetify/src/composables/theme.ts b/packages/vuetify/src/composables/theme.ts index f9aeca9bd250..a261035cd333 100644 --- a/packages/vuetify/src/composables/theme.ts +++ b/packages/vuetify/src/composables/theme.ts @@ -255,7 +255,7 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install: const styles = computed(() => { const lines: string[] = [] - if (current.value.dark) { + if (current.value?.dark) { createCssClass(lines, ':root', ['color-scheme: dark']) } diff --git a/packages/vuetify/src/composables/transition.ts b/packages/vuetify/src/composables/transition.ts index 98458bb84e06..79c088f00763 100644 --- a/packages/vuetify/src/composables/transition.ts +++ b/packages/vuetify/src/composables/transition.ts @@ -35,7 +35,7 @@ export const MaybeTransition: FunctionalComponent<MaybeTransitionProps> = (props : customProps as any, typeof transition === 'string' ? {} - : { disabled, group }, + : Object.fromEntries(Object.entries({ disabled, group }).filter(([_, v]) => v !== undefined)), rest as any, ), slots diff --git a/packages/vuetify/src/composables/validation.ts b/packages/vuetify/src/composables/validation.ts index 347eeec9f089..2b250cb02d55 100644 --- a/packages/vuetify/src/composables/validation.ts +++ b/packages/vuetify/src/composables/validation.ts @@ -166,15 +166,16 @@ export function useValidation ( form?.update(uid.value, isValid.value, errorMessages.value) }) - function reset () { + async function reset () { model.value = null - nextTick(resetValidation) + await nextTick() + await resetValidation() } - function resetValidation () { + async function resetValidation () { isPristine.value = true if (!validateOn.value.lazy) { - validate(true) + await validate(true) } else { internalErrorMessages.value = [] } diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.sass b/packages/vuetify/src/labs/VNumberInput/VNumberInput.sass new file mode 100644 index 000000000000..43b276350598 --- /dev/null +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.sass @@ -0,0 +1,47 @@ +@use 'sass:selector' +@use './variables' as * + +.v-number-input + $root: & + $control-root: #{selector.append($root, '__control')} + + input[type="number"] + -moz-appearance: textfield + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button + -webkit-appearance: none + + .v-field + padding-inline-end: 0 + padding-inline-start: 0 + + &--inset + .v-divider + height: $number-input-inset-divider-size + width: $number-input-inset-divider-size + align-self: center + + &--split + .v-field__input + text-align: center + + &--stacked + #{$control-root} + flex-direction: column-reverse + .v-btn + flex: 1 + + &--hide-input + .v-field + flex: none + &__input + width: 0 + padding-inline: 0 + + &__control + display: flex + height: 100% + + .v-btn + background-color: transparent diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx new file mode 100644 index 000000000000..917827ff9771 --- /dev/null +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx @@ -0,0 +1,289 @@ +// Styles +import './VNumberInput.sass' + +// Components +import { VBtn } from '../../components/VBtn' +import { VDefaultsProvider } from '../../components/VDefaultsProvider' +import { VDivider } from '../../components/VDivider' +import { filterFieldProps, makeVFieldProps, VField } from '@/components/VField/VField' +import { makeVInputProps, VInput } from '@/components/VInput/VInput' + +// Composables +import { makeFocusProps, useFocus } from '@/composables/focus' +import { useProxiedModel } from '@/composables/proxiedModel' + +// Utilities +import { computed, ref } from 'vue' +import { filterInputAttrs, genericComponent, only, propsFactory, useRender } from '@/util' + +// Types +import type { PropType } from 'vue' +import type { VFieldSlots } from '@/components/VField/VField' +import type { VInputSlots } from '@/components/VInput/VInput' + +type ControlSlot = { + click: () => void +} + +type VNumberInputSlots = Omit<VInputSlots & VFieldSlots, 'default'> & { + increment: ControlSlot + decrement: ControlSlot +} + +type ControlVariant = 'default' | 'stacked' | 'split' + +const makeVNumberInputProps = propsFactory({ + controlVariant: { + type: String as PropType<ControlVariant>, + default: 'default', + }, + inset: Boolean, + hideInput: Boolean, + min: Number, + max: Number, + step: Number, + + ...only(makeVInputProps(), [ + 'density', + 'disabled', + 'focused', + 'hideDetails', + 'hint', + 'label', + 'persistentHint', + 'readonly', + ]), + ...only(makeVFieldProps(), [ + 'baseColor', + 'bgColor', + 'class', + 'color', + 'disabled', + 'error', + 'loading', + 'reverse', + 'rounded', + 'style', + 'theme', + 'variant', + ]), + ...makeFocusProps(), +}, 'VNumberInput') + +export const VNumberInput = genericComponent<VNumberInputSlots>()({ + name: 'VNumberInput', + + inheritAttrs: false, + + props: { + ...makeVNumberInputProps(), + + modelValue: { + type: [Number, String], + default: 0, + }, + }, + + emits: { + 'update:modelValue': (val: number) => true, + }, + + setup (props, { attrs, emit, slots }) { + const model = useProxiedModel(props, 'modelValue') + const { isFocused, focus, blur } = useFocus(props) + const inputRef = ref<HTMLInputElement>() + + function onFocus () { + if (!isFocused.value) focus() + } + + const controlVariant = computed(() => { + return props.hideInput ? 'stacked' : props.controlVariant + }) + + function toggleUpDown (increment = true) { + if (increment) { + inputRef.value?.stepUp() + } else { + inputRef.value?.stepDown() + } + + if (inputRef.value) model.value = parseInt(inputRef.value.value, 10) + } + + function onClickUp () { + toggleUpDown() + } + + function onClickDown () { + toggleUpDown(false) + } + + const incrementSlotProps = computed(() => ({ click: onClickUp })) + + const decrementSlotProps = computed(() => ({ click: onClickDown })) + + useRender(() => { + const fieldProps = filterFieldProps(props) + const [rootAttrs, inputAttrs] = filterInputAttrs(attrs) + const { modelValue: _, ...inputProps } = VInput.filterProps(props) + + function controlNode () { + const defaultHeight = controlVariant.value === 'stacked' ? 'auto' : '100%' + return ( + <div class="v-number-input__control"> + { + !slots.decrement ? ( + <VBtn + flat + key="decrement-btn" + height={ defaultHeight } + icon="$expand" + rounded="0" + size="small" + onClick={ onClickDown } + /> + ) : ( + <VDefaultsProvider + key="decrement-defaults" + defaults={{ + VBtn: { + flat: true, + rounded: '0', + height: defaultHeight, + size: 'small', + icon: '$expand', + }, + }} + > + { slots.decrement(decrementSlotProps.value) } + </VDefaultsProvider> + ) + } + + <VDivider + vertical={ controlVariant.value !== 'stacked' } + /> + + { + !slots.increment ? ( + <VBtn + flat + key="increment-btn" + height={ defaultHeight } + icon="$collapse" + onClick={ onClickUp } + rounded="0" + size="small" + /> + ) : ( + <VDefaultsProvider + key="increment-defaults" + defaults={{ + VBtn: { + flat: true, + height: defaultHeight, + rounded: '0', + size: 'small', + icon: '$collapse', + }, + }} + > + { slots.increment(incrementSlotProps.value) } + </VDefaultsProvider> + ) + } + </div> + ) + } + + function dividerNode () { + return !props.hideInput && !props.inset ? <VDivider vertical /> : undefined + } + + return ( + <VInput + class={[ + 'v-number-input', + { + 'v-number-input--default': controlVariant.value === 'default', + 'v-number-input--hide-input': props.hideInput, + 'v-number-input--inset': props.inset, + 'v-number-input--reverse': props.reverse, + 'v-number-input--split': controlVariant.value === 'split', + 'v-number-input--stacked': controlVariant.value === 'stacked', + }, + props.class, + ]} + { ...rootAttrs } + { ...inputProps } + focused={ isFocused.value } + style={ props.style } + > + {{ + ...slots, + default: () => ( + <VField + { ...fieldProps } + active + focused={ isFocused.value } + > + {{ + ...slots, + default: ({ + props: { class: fieldClass, ...slotProps }, + }) => ( + <input + ref={ inputRef } + type="number" + value={ model.value } + class={ fieldClass } + max={ props.max } + min={ props.min } + step={ props.step } + onFocus={ onFocus } + onBlur={ blur } + { ...inputAttrs } + /> + ), + 'append-inner': controlVariant.value === 'split' ? () => ( + <div class="v-number-input__control"> + <VDivider vertical /> + + <VBtn + flat + height="100%" + icon="$plus" + tile + onClick={ onClickUp } + /> + </div> + ) : (!props.reverse + ? () => <>{ dividerNode() }{ controlNode() }</> + : undefined), + 'prepend-inner': controlVariant.value === 'split' ? () => ( + <div class="v-number-input__control"> + <VBtn + flat + height="100%" + icon="$minus" + tile + onClick={ onClickDown } + /> + + <VDivider vertical /> + </div> + ) : (props.reverse + ? () => <>{ controlNode() }{ dividerNode() }</> + : undefined), + }} + </VField> + ), + }} + </VInput> + ) + }) + }, +}) + +export type VNumberInput = InstanceType<typeof VNumberInput> diff --git a/packages/vuetify/src/labs/VNumberInput/_variables.scss b/packages/vuetify/src/labs/VNumberInput/_variables.scss new file mode 100644 index 000000000000..16af2fe55be3 --- /dev/null +++ b/packages/vuetify/src/labs/VNumberInput/_variables.scss @@ -0,0 +1 @@ +$number-input-inset-divider-size: 55% !default; diff --git a/packages/vuetify/src/labs/VNumberInput/index.ts b/packages/vuetify/src/labs/VNumberInput/index.ts new file mode 100644 index 000000000000..20aa5db57c50 --- /dev/null +++ b/packages/vuetify/src/labs/VNumberInput/index.ts @@ -0,0 +1 @@ +export { VNumberInput } from './VNumberInput' diff --git a/packages/vuetify/src/labs/components.ts b/packages/vuetify/src/labs/components.ts index 695e5631bf6a..f1a34406da6f 100644 --- a/packages/vuetify/src/labs/components.ts +++ b/packages/vuetify/src/labs/components.ts @@ -2,6 +2,7 @@ export * from './VCalendar' export * from './VConfirmEdit' export * from './VEmptyState' export * from './VFab' +export * from './VNumberInput' export * from './VPicker' export * from './VSparkline' export * from './VSpeedDial' diff --git a/packages/vuetify/src/locale/no.ts b/packages/vuetify/src/locale/no.ts index 01d33e5de978..9b759347077c 100644 --- a/packages/vuetify/src/locale/no.ts +++ b/packages/vuetify/src/locale/no.ts @@ -1,6 +1,6 @@ export default { badge: 'Skilt', - open: 'Open', + open: 'Åpne', close: 'Lukk', confirmEdit: { ok: 'OK', diff --git a/yarn.lock b/yarn.lock index 94d493a1a93d..ff1736f95427 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3949,10 +3949,10 @@ find-cache-dir "^3.3.2" upath "^2.0.1" -"@vuetify/one@^1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@vuetify/one/-/one-1.2.4.tgz#0cd7756100fe2158fc98bdafef61b91abcbdeb02" - integrity sha512-Q9Os2+3RdaVv+oxxF+OdJCk+CH7PWVNHJY83Di7LM/9yr/npnW/YyDpoq1pfyHMzuLkfyl8XuKc13izV1D5fDA== +"@vuetify/one@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@vuetify/one/-/one-1.5.0.tgz#c76df35ca8ec7fb55abedfe9d1523fc58d3d5ad5" + integrity sha512-nuMPMnMcR87FkbhC8NW0B0fI0S57LXkR6/Zy3dxPGMOoSZ2L5BFk2BYq2fUJ5xtUnghA9SRUcagT6XXLG1mlwA== "@vueuse/head@^1.3.1": version "1.3.1"