From 3bb61d99c880d98376b71e5201bba3fe18cfbafc Mon Sep 17 00:00:00 2001 From: John Leider Date: Wed, 10 Jul 2024 11:38:55 -0500 Subject: [PATCH 01/22] docs(buttons/cards/why-vuetify): update entry promotion --- packages/docs/src/pages/en/components/buttons.md | 2 +- packages/docs/src/pages/en/components/cards.md | 2 +- packages/docs/src/pages/en/introduction/why-vuetify.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/docs/src/pages/en/components/buttons.md b/packages/docs/src/pages/en/components/buttons.md index 0b8e586a2f0..2391dc6349f 100644 --- a/packages/docs/src/pages/en/components/buttons.md +++ b/packages/docs/src/pages/en/components/buttons.md @@ -24,7 +24,7 @@ The `v-btn` component replaces the standard html button with a material design t - + ## Usage diff --git a/packages/docs/src/pages/en/components/cards.md b/packages/docs/src/pages/en/components/cards.md index 148ebd52d76..ed260d8e50a 100644 --- a/packages/docs/src/pages/en/components/cards.md +++ b/packages/docs/src/pages/en/components/cards.md @@ -24,7 +24,7 @@ features: - + ## Usage diff --git a/packages/docs/src/pages/en/introduction/why-vuetify.md b/packages/docs/src/pages/en/introduction/why-vuetify.md index 048577a7981..5ac6e1cb89b 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 - + ## What is Vuetify? From 8ac10e43a10ea3f966f1bb7cd9d87fdff52c93c9 Mon Sep 17 00:00:00 2001 From: Zhaolin Liang Date: Thu, 11 Jul 2024 00:55:24 +0800 Subject: [PATCH 02/22] fix(VDataTable): support groupBy when sorting is disabled (#20047) fixes #20046 Co-authored-by: John Leider --- .../src/components/VDataTable/VDataTable.tsx | 5 +- .../VDataTable/VDataTableServer.tsx | 5 +- .../VDataTable/VDataTableVirtual.tsx | 5 +- .../__tests__/VDataTable.spec.cy.tsx | 99 +++++++++++++++++++ .../VDataTable/composables/group.ts | 10 +- .../components/VDataTable/composables/sort.ts | 3 +- 6 files changed, 116 insertions(+), 11 deletions(-) diff --git a/packages/vuetify/src/components/VDataTable/VDataTable.tsx b/packages/vuetify/src/components/VDataTable/VDataTable.tsx index 830538b1acb..c98c3ba8147 100644 --- a/packages/vuetify/src/components/VDataTable/VDataTable.tsx +++ b/packages/vuetify/src/components/VDataTable/VDataTable.tsx @@ -21,7 +21,7 @@ import { provideDefaults } from '@/composables/defaults' import { makeFilterProps, useFilter } from '@/composables/filter' // Utilities -import { computed, toRef } from 'vue' +import { computed, toRef, toRefs } from 'vue' import { genericComponent, propsFactory, useRender } from '@/util' // Types @@ -130,6 +130,7 @@ export const VDataTable = genericComponent( const { groupBy } = createGroupBy(props) const { sortBy, multiSort, mustSort } = createSort(props) const { page, itemsPerPage } = createPagination(props) + const { disableSort } = toRefs(props) const { columns, @@ -152,7 +153,7 @@ export const VDataTable = genericComponent( }) const { toggleSort } = provideSort({ sortBy, multiSort, mustSort, page }) - const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy }) + const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy, disableSort }) const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, { transform: item => item.columns, diff --git a/packages/vuetify/src/components/VDataTable/VDataTableServer.tsx b/packages/vuetify/src/components/VDataTable/VDataTableServer.tsx index f3e9c5293aa..13005194dc3 100644 --- a/packages/vuetify/src/components/VDataTable/VDataTableServer.tsx +++ b/packages/vuetify/src/components/VDataTable/VDataTableServer.tsx @@ -18,7 +18,7 @@ import { createSort, provideSort } from './composables/sort' import { provideDefaults } from '@/composables/defaults' // Utilities -import { computed, provide, toRef } from 'vue' +import { computed, provide, toRef, toRefs } from 'vue' import { genericComponent, propsFactory, useRender } from '@/util' // Types @@ -69,6 +69,7 @@ export const VDataTableServer = genericComponent parseInt(props.itemsLength, 10)) const { columns, headers } = createHeaders(props, { @@ -81,7 +82,7 @@ export const VDataTableServer = genericComponent item.columns, diff --git a/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx b/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx index 0cc1a79df2c..cde77da1731 100644 --- a/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx +++ b/packages/vuetify/src/components/VDataTable/__tests__/VDataTable.spec.cy.tsx @@ -14,6 +14,7 @@ const DESSERT_HEADERS = [ { title: 'Carbs (g)', key: 'carbs' }, { title: 'Protein (g)', key: 'protein' }, { title: 'Iron (%)', key: 'iron' }, + { title: 'Group', key: 'group' }, ] const DESSERT_ITEMS = [ @@ -24,6 +25,7 @@ const DESSERT_ITEMS = [ carbs: 24, protein: 4.0, iron: '1%', + group: 1, }, { name: 'Ice cream sandwich', @@ -32,6 +34,7 @@ const DESSERT_ITEMS = [ carbs: 37, protein: 4.3, iron: '1%', + group: 3, }, { name: 'Eclair', @@ -40,6 +43,7 @@ const DESSERT_ITEMS = [ carbs: 23, protein: 6.0, iron: '7%', + group: 2, }, { name: 'Cupcake', @@ -48,6 +52,7 @@ const DESSERT_ITEMS = [ carbs: 67, protein: 4.3, iron: '8%', + group: 2, }, { name: 'Gingerbread', @@ -56,6 +61,7 @@ const DESSERT_ITEMS = [ carbs: 49, protein: 3.9, iron: '16%', + group: 3, }, { name: 'Jelly bean', @@ -64,6 +70,7 @@ const DESSERT_ITEMS = [ carbs: 94, protein: 0.0, iron: '0%', + group: 1, }, { name: 'Lollipop', @@ -72,6 +79,7 @@ const DESSERT_ITEMS = [ carbs: 98, protein: 0, iron: '2%', + group: 2, }, { name: 'Honeycomb', @@ -80,6 +88,7 @@ const DESSERT_ITEMS = [ carbs: 87, protein: 6.5, iron: '45%', + group: 3, }, { name: 'Donut', @@ -88,6 +97,7 @@ const DESSERT_ITEMS = [ carbs: 51, protein: 4.9, iron: '22%', + group: 3, }, { name: 'KitKat', @@ -96,6 +106,7 @@ const DESSERT_ITEMS = [ carbs: 65, protein: 7, iron: '6%', + group: 1, }, ] @@ -356,4 +367,92 @@ describe('VDataTable', () => { cy.get('.v-data-table').find('h3').should('exist') }) }) + + describe('sort', () => { + it('should sort by sortBy', () => { + cy.mount(() => ( + + + + )) + cy.get('thead .v-data-table__td').eq(2).should('have.class', 'v-data-table__th--sorted') + .get('tbody td:nth-child(3)').then(rows => { + const actualFat = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedFat = DESSERT_ITEMS.map(d => d.fat).sort((a, b) => a - b) + expect(actualFat).to.deep.equal(expectedFat) + }) + cy.get('thead .v-data-table__td').eq(2).click() + .get('thead .v-data-table__td').eq(2).should('have.class', 'v-data-table__th--sorted') + .get('tbody td:nth-child(3)').then(rows => { + const actualFat = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedFat = DESSERT_ITEMS.map(d => d.fat).sort((a, b) => b - a) + expect(actualFat).to.deep.equal(expectedFat) + }) + }) + + it('should sort by groupBy and sortBy', () => { + cy.mount(() => ( + + + + )).get('tr.v-data-table-group-header-row .v-data-table__td button + span').then(rows => { + const actualGroup = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedGroup = [...new Set(DESSERT_ITEMS.map(d => d.group))].sort((a, b) => b - a) + expect(actualGroup).to.deep.equal(expectedGroup) + }).get('.v-data-table-group-header-row button').eq(0).click() + .get('.v-data-table__tr td:nth-child(3)').then(rows => { + const actualCalories = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedCalories = DESSERT_ITEMS.filter(d => d.group === 3).map(d => d.calories).sort((a, b) => b - a) + expect(actualCalories).to.deep.equal(expectedCalories) + }) + }) + + // https://github.com/vuetifyjs/vuetify/issues/20046 + it('should sort by groupBy while sort is disabled', () => { + cy.mount(() => ( + + + + )).get('tr.v-data-table-group-header-row .v-data-table__td button + span').then(rows => { + const actualGroup = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedGroup = [...new Set(DESSERT_ITEMS.map(d => d.group))].sort((a, b) => b - a) + expect(actualGroup).to.deep.equal(expectedGroup) + }).get('.v-data-table-group-header-row button').eq(0).click() + .get('.v-data-table__tr td:nth-child(3)').then(rows => { + const actualCalories = Array.from(rows).map(row => { + return Number(row.textContent) + }) + const expectedCalories = DESSERT_ITEMS.filter(d => d.group === 3).map(d => d.calories) + expect(actualCalories).to.deep.equal(expectedCalories) + }) + }) + }) }) diff --git a/packages/vuetify/src/components/VDataTable/composables/group.ts b/packages/vuetify/src/components/VDataTable/composables/group.ts index b66d3b3c14e..7f4ac2be468 100644 --- a/packages/vuetify/src/components/VDataTable/composables/group.ts +++ b/packages/vuetify/src/components/VDataTable/composables/group.ts @@ -51,15 +51,19 @@ export function createGroupBy (props: GroupProps) { return { groupBy } } -export function provideGroupBy (options: { groupBy: Ref, sortBy: Ref }) { - const { groupBy, sortBy } = options +export function provideGroupBy (options: { + groupBy: Ref + sortBy: Ref + disableSort?: Ref +}) { + const { disableSort, groupBy, sortBy } = options const opened = ref(new Set()) const sortByWithGroups = computed(() => { return groupBy.value.map(val => ({ ...val, order: val.order ?? false, - })).concat(sortBy.value) + })).concat(disableSort?.value ? [] : sortBy.value) }) function isGroupOpen (group: Group) { diff --git a/packages/vuetify/src/components/VDataTable/composables/sort.ts b/packages/vuetify/src/components/VDataTable/composables/sort.ts index 0c9beba1c2f..d2e316ddf96 100644 --- a/packages/vuetify/src/components/VDataTable/composables/sort.ts +++ b/packages/vuetify/src/components/VDataTable/composables/sort.ts @@ -98,7 +98,6 @@ export function useSort () { export function useSortedItems ( props: { customKeySort: Record | undefined - disableSort?: Boolean }, items: Ref, sortBy: Ref, @@ -110,7 +109,7 @@ export function useSortedItems ( ) { const locale = useLocale() const sortedItems = computed(() => { - if (!sortBy.value.length || props.disableSort) return items.value + if (!sortBy.value.length) return items.value return sortItems(items.value, sortBy.value, locale.current.value, { transform: options?.transform, From 437fbce80c8567b32a34059997adf2d08818664f Mon Sep 17 00:00:00 2001 From: John Leider Date: Wed, 10 Jul 2024 15:36:12 -0500 Subject: [PATCH 03/22] docs(Toc): update logic that determines if the Spot is shown --- packages/docs/src/components/app/Toc.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/src/components/app/Toc.vue b/packages/docs/src/components/app/Toc.vue index 22814196c7e..5daa54a2174 100644 --- a/packages/docs/src/components/app/Toc.vue +++ b/packages/docs/src/components/app/Toc.vue @@ -104,7 +104,7 @@ Date: Thu, 11 Jul 2024 11:02:24 -0500 Subject: [PATCH 04/22] fix(VNavigationDrawer): don't calculate transform width if temporary fixes #20143 --- .../src/components/VNavigationDrawer/VNavigationDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx index ef077b7c8da..1d45358649f 100644 --- a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx +++ b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx @@ -177,7 +177,7 @@ export const VNavigationDrawer = genericComponent()({ return isDragging.value ? size * dragProgress.value : size }) - const elementSize = computed(() => ['top', 'bottom'].includes(props.location) ? 0 : width.value) + const elementSize = computed(() => ['top', 'bottom'].includes(props.location) || props.temporary ? 0 : width.value) const { layoutItemStyles, layoutItemScrimStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), From 8f871fc0a35bea8e5b50fdc557e15ee3c952615c Mon Sep 17 00:00:00 2001 From: Kael Date: Fri, 12 Jul 2024 02:19:45 +1000 Subject: [PATCH 05/22] chore: update workspace root name --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index df2ba25274d..33e537c557a 100755 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "name": "@vuetify/vuetify-root", "private": true, "workspaces": [ "packages/*" From bc2f96a7d1e8f46eacd0704e986a978dc4b556d8 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sun, 14 Jul 2024 10:15:26 +1000 Subject: [PATCH 06/22] fix(VField): allow center-affix to set singleLine only if it is null fixes #20134 --- packages/api-generator/src/locale/en/VField.json | 2 +- packages/vuetify/src/components/VField/VField.tsx | 7 +++++-- packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/api-generator/src/locale/en/VField.json b/packages/api-generator/src/locale/en/VField.json index 82bc8788628..a03c8df9305 100644 --- a/packages/api-generator/src/locale/en/VField.json +++ b/packages/api-generator/src/locale/en/VField.json @@ -2,7 +2,7 @@ "props": { "appendInnerIcon": "Creates a [v-icon](/api/v-icon/) component in the **append-inner** slot.", "baseColor": "Sets the color of the input when it is not focused.", - "centerAffix": "Automatically apply **[singleLine](/api/v-field/#props-single-line)** under the hood, and vertically align **appendInner**, **prependInner**, **clearIcon**, **label** and **input field** in the center.", + "centerAffix": "Automatically apply **[singleLine](/api/v-field/#props-single-line)** when it is not explicitly set to a Boolean (when it is `null` by default), and vertically align **appendInner**, **prependInner**, **clearIcon**, **label** and **input field** in the center.", "clearIcon": "The icon used when the **clearable** prop is set to true.", "dirty": "Manually apply the dirty state styling.", "disabled": "Removes the ability to click or target the input.", diff --git a/packages/vuetify/src/components/VField/VField.tsx b/packages/vuetify/src/components/VField/VField.tsx index 037cf02a23a..df98ece2e4e 100644 --- a/packages/vuetify/src/components/VField/VField.tsx +++ b/packages/vuetify/src/components/VField/VField.tsx @@ -76,7 +76,10 @@ export const makeVFieldProps = propsFactory({ persistentClear: Boolean, prependInnerIcon: IconValue, reverse: Boolean, - singleLine: Boolean, + singleLine: { + type: Boolean, + default: null, + }, variant: { type: String as PropType, default: 'filled', @@ -133,7 +136,7 @@ export const VField = genericComponent( const { roundedClasses } = useRounded(props) const { rtlClasses } = useRtl() - const isSingleLine = computed(() => props.singleLine || props.centerAffix) + const isSingleLine = computed(() => props.singleLine != null ? props.singleLine : props.centerAffix) const isActive = computed(() => props.dirty || props.active) const hasLabel = computed(() => !isSingleLine.value && !!(props.label || slots.label)) diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx index 75d77da5ef7..1812eaa0159 100644 --- a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx @@ -293,6 +293,8 @@ export const VNumberInput = genericComponent()({ props.class, ]} { ...textFieldProps } + centerAffix={ true } + singleLine={ false } style={ props.style } inputmode="decimal" > From b1f74e1703389b4e0c3ac7e2dbca44d378d31387 Mon Sep 17 00:00:00 2001 From: Yuchao Wu Date: Sun, 14 Jul 2024 10:35:10 +1000 Subject: [PATCH 07/22] chore: fix lint issue --- packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx index 1812eaa0159..c025f166163 100644 --- a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx @@ -293,8 +293,8 @@ export const VNumberInput = genericComponent()({ props.class, ]} { ...textFieldProps } - centerAffix={ true } - singleLine={ false } + centerAffix + singleLine={ false } style={ props.style } inputmode="decimal" > From 098f8029d5376152145ab4590da086cb59419f8f Mon Sep 17 00:00:00 2001 From: Yuchao Date: Wed, 17 Jul 2024 04:29:43 +1000 Subject: [PATCH 08/22] Revert "fix(VField/VInput): centerAffix prop for underlined/plain (#20064)" (#20173) --- .../api-generator/src/locale/en/VField.json | 2 +- .../api-generator/src/locale/en/VInput.json | 2 +- .../vuetify/src/components/VField/VField.sass | 8 ++------ .../vuetify/src/components/VField/VField.tsx | 17 ++++++++--------- .../vuetify/src/components/VInput/VInput.sass | 2 +- .../vuetify/src/components/VInput/VInput.tsx | 5 ++++- .../src/components/VTextField/VTextField.tsx | 2 +- .../src/components/VTextarea/VTextarea.tsx | 10 +++++----- .../src/labs/VNumberInput/VNumberInput.tsx | 2 -- 9 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/api-generator/src/locale/en/VField.json b/packages/api-generator/src/locale/en/VField.json index a03c8df9305..2ce083c09fc 100644 --- a/packages/api-generator/src/locale/en/VField.json +++ b/packages/api-generator/src/locale/en/VField.json @@ -2,7 +2,7 @@ "props": { "appendInnerIcon": "Creates a [v-icon](/api/v-icon/) component in the **append-inner** slot.", "baseColor": "Sets the color of the input when it is not focused.", - "centerAffix": "Automatically apply **[singleLine](/api/v-field/#props-single-line)** when it is not explicitly set to a Boolean (when it is `null` by default), and vertically align **appendInner**, **prependInner**, **clearIcon**, **label** and **input field** in the center.", + "centerAffix": "Vertically align **appendInner**, **prependInner**, **clearIcon** and **label** in the center.", "clearIcon": "The icon used when the **clearable** prop is set to true.", "dirty": "Manually apply the dirty state styling.", "disabled": "Removes the ability to click or target the input.", diff --git a/packages/api-generator/src/locale/en/VInput.json b/packages/api-generator/src/locale/en/VInput.json index d6f181d023a..47f3a7804c3 100644 --- a/packages/api-generator/src/locale/en/VInput.json +++ b/packages/api-generator/src/locale/en/VInput.json @@ -1,7 +1,7 @@ { "props": { "backgroundColor": "Changes the background-color of the input.", - "centerAffix": "Vertically align **append** and **prepend** in the center.", + "centerAffix": "Vertically align **appendInner**, **prependInner**, **clearIcon** and **label** in the center.", "direction": "Changes the direction of the input.", "hideDetails": "Hides hint and validation errors. When set to `auto` messages will be rendered only if there's a message (hint, error message, counter value etc) to display.", "hideSpinButtons": "Hides spin buttons on the input when type is set to `number`.", diff --git a/packages/vuetify/src/components/VField/VField.sass b/packages/vuetify/src/components/VField/VField.sass index 6a698fabcf4..051542e5bdb 100644 --- a/packages/vuetify/src/components/VField/VField.sass +++ b/packages/vuetify/src/components/VField/VField.sass @@ -141,10 +141,6 @@ $root: & - @at-root #{selector.nest('.v-field.v-field--center-affix.v-field--variant-underlined, .v-field.v-field--center-affix.v-field--variant-plain', &)} - padding-top: unset - padding-bottom: unset - @at-root @include tools.density('v-input', $input-density) using ($modifier) @at-root #{selector.nest(&, $root)} @@ -199,8 +195,8 @@ align-items: center padding-top: 0 - .v-field:not(.v-field--center-affix).v-field--variant-underlined, - .v-field:not(.v-field--center-affix).v-field--variant-plain + .v-field.v-field--variant-underlined, + .v-field.v-field--variant-plain .v-field__append-inner, .v-field__clearable, .v-field__prepend-inner diff --git a/packages/vuetify/src/components/VField/VField.tsx b/packages/vuetify/src/components/VField/VField.tsx index df98ece2e4e..b3d658c127a 100644 --- a/packages/vuetify/src/components/VField/VField.tsx +++ b/packages/vuetify/src/components/VField/VField.tsx @@ -62,7 +62,10 @@ export const makeVFieldProps = propsFactory({ default: '$clear', }, active: Boolean, - centerAffix: Boolean, + centerAffix: { + type: Boolean, + default: undefined, + }, color: String, baseColor: String, dirty: Boolean, @@ -76,10 +79,7 @@ export const makeVFieldProps = propsFactory({ persistentClear: Boolean, prependInnerIcon: IconValue, reverse: Boolean, - singleLine: { - type: Boolean, - default: null, - }, + singleLine: Boolean, variant: { type: String as PropType, default: 'filled', @@ -136,9 +136,8 @@ export const VField = genericComponent( const { roundedClasses } = useRounded(props) const { rtlClasses } = useRtl() - const isSingleLine = computed(() => props.singleLine != null ? props.singleLine : props.centerAffix) const isActive = computed(() => props.dirty || props.active) - const hasLabel = computed(() => !isSingleLine.value && !!(props.label || slots.label)) + const hasLabel = computed(() => !props.singleLine && !!(props.label || slots.label)) const uid = getUid() const id = computed(() => props.id || `input-${uid}`) @@ -243,7 +242,7 @@ export const VField = genericComponent( { 'v-field--active': isActive.value, 'v-field--appended': hasAppend, - 'v-field--center-affix': props.centerAffix, + 'v-field--center-affix': props.centerAffix ?? !isPlainOrUnderlined.value, 'v-field--disabled': props.disabled, 'v-field--dirty': props.dirty, 'v-field--error': props.error, @@ -252,7 +251,7 @@ export const VField = genericComponent( 'v-field--persistent-clear': props.persistentClear, 'v-field--prepended': hasPrepend, 'v-field--reverse': props.reverse, - 'v-field--single-line': isSingleLine.value, + 'v-field--single-line': props.singleLine, 'v-field--no-label': !label(), [`v-field--variant-${props.variant}`]: true, }, diff --git a/packages/vuetify/src/components/VInput/VInput.sass b/packages/vuetify/src/components/VInput/VInput.sass index 2441ec1dde6..11ded05238b 100644 --- a/packages/vuetify/src/components/VInput/VInput.sass +++ b/packages/vuetify/src/components/VInput/VInput.sass @@ -103,7 +103,7 @@ input[type=number] -moz-appearance: textfield - &--plain-underlined:not(&--center-affix) + &--plain-underlined .v-input__prepend, .v-input__append diff --git a/packages/vuetify/src/components/VInput/VInput.tsx b/packages/vuetify/src/components/VInput/VInput.tsx index f12b059b9d7..4234aeb0aad 100644 --- a/packages/vuetify/src/components/VInput/VInput.tsx +++ b/packages/vuetify/src/components/VInput/VInput.tsx @@ -40,7 +40,10 @@ export interface VInputSlot { export const makeVInputProps = propsFactory({ id: String, appendIcon: IconValue, - centerAffix: Boolean, + centerAffix: { + type: Boolean, + default: true, + }, prependIcon: IconValue, hideDetails: [Boolean, String] as PropType, hideSpinButtons: Boolean, diff --git a/packages/vuetify/src/components/VTextField/VTextField.tsx b/packages/vuetify/src/components/VTextField/VTextField.tsx index 3e835143866..c2f09caf664 100644 --- a/packages/vuetify/src/components/VTextField/VTextField.tsx +++ b/packages/vuetify/src/components/VTextField/VTextField.tsx @@ -176,6 +176,7 @@ export const VTextField = genericComponent()({ style={ props.style } { ...rootAttrs } { ...inputProps } + centerAffix={ !isPlainOrUnderlined.value } focused={ isFocused.value } > {{ @@ -201,7 +202,6 @@ export const VTextField = genericComponent()({ dirty={ isDirty.value || props.dirty } disabled={ isDisabled.value } focused={ isFocused.value } - centerAffix={ props.centerAffix } error={ isValid.value === false } > {{ diff --git a/packages/vuetify/src/components/VTextarea/VTextarea.tsx b/packages/vuetify/src/components/VTextarea/VTextarea.tsx index e675b3c0c1f..3d8a93007a1 100644 --- a/packages/vuetify/src/components/VTextarea/VTextarea.tsx +++ b/packages/vuetify/src/components/VTextarea/VTextarea.tsx @@ -18,7 +18,7 @@ import Intersect from '@/directives/intersect' // Utilities import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch, watchEffect } from 'vue' -import { callEvent, clamp, convertToUnit, filterInputAttrs, genericComponent, omit, propsFactory, useRender } from '@/util' +import { callEvent, clamp, convertToUnit, filterInputAttrs, genericComponent, propsFactory, useRender } from '@/util' // Types import type { PropType } from 'vue' @@ -48,8 +48,8 @@ export const makeVTextareaProps = propsFactory({ suffix: String, modelModifiers: Object as PropType>, - ...omit(makeVInputProps(), ['centerAffix']), - ...omit(makeVFieldProps(), ['centerAffix']), + ...makeVInputProps(), + ...makeVFieldProps(), }, 'VTextarea') type VTextareaSlots = Omit & { @@ -228,7 +228,7 @@ export const VTextarea = genericComponent()({ style={ props.style } { ...rootAttrs } { ...inputProps } - centerAffix={ false } + centerAffix={ rows.value === 1 && !isPlainOrUnderlined.value } focused={ isFocused.value } > {{ @@ -253,7 +253,7 @@ export const VTextarea = genericComponent()({ { ...fieldProps } id={ id.value } active={ isActive.value || isDirty.value } - centerAffix={ false } + centerAffix={ rows.value === 1 && !isPlainOrUnderlined.value } dirty={ isDirty.value || props.dirty } disabled={ isDisabled.value } focused={ isFocused.value } diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx index c025f166163..75d77da5ef7 100644 --- a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx @@ -293,8 +293,6 @@ export const VNumberInput = genericComponent()({ props.class, ]} { ...textFieldProps } - centerAffix - singleLine={ false } style={ props.style } inputmode="decimal" > From 0c80102ecca9dffb337b974064f3e583c5467e63 Mon Sep 17 00:00:00 2001 From: Blaine Landowski <42892508+blalan05@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:41:57 -0500 Subject: [PATCH 09/22] fix(VTimePicker): don't show buttons if ampmInTitle is not true (#20178) --- packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx index f8ab7fd5983..ba65c355ade 100644 --- a/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx +++ b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx @@ -17,6 +17,7 @@ type Period = 'am' | 'pm' export const makeVTimePickerControlsProps = propsFactory({ ampm: Boolean, + ampmInTitle: Boolean, ampmReadonly: Boolean, color: String, disabled: Boolean, @@ -122,7 +123,7 @@ export const VTimePickerControls = genericComponent()({ } { - props.ampm && ( + props.ampm && props.ampmInTitle && (
Date: Wed, 17 Jul 2024 02:49:33 +0800 Subject: [PATCH 10/22] fix(VBtn): allow stacked and block props to work together (#20162) Co-authored-by: John Leider --- packages/vuetify/src/components/VBtn/VBtn.sass | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vuetify/src/components/VBtn/VBtn.sass b/packages/vuetify/src/components/VBtn/VBtn.sass index 8ed3b7b0f7f..3acf7c34624 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.sass +++ b/packages/vuetify/src/components/VBtn/VBtn.sass @@ -168,6 +168,9 @@ .v-icon --v-icon-size-multiplier: #{calc(24/21)} + &.v-btn--block + min-width: 100% + .v-btn__loader align-items: center display: flex From bd1f63dcfc57ef0b15b8cfd2847bd40ba7751a39 Mon Sep 17 00:00:00 2001 From: Mathieu Guilbault Date: Tue, 16 Jul 2024 14:54:01 -0400 Subject: [PATCH 11/22] fix: augment GlobalComponents in multiple vue modules (#20144) Co-authored-by: Mathieu Guilbault --- packages/vuetify/src/shims.d.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/vuetify/src/shims.d.ts b/packages/vuetify/src/shims.d.ts index ac4f3520636..12ab5fb90dc 100644 --- a/packages/vuetify/src/shims.d.ts +++ b/packages/vuetify/src/shims.d.ts @@ -16,9 +16,12 @@ declare global { } } } - +interface _GlobalComponents { + // @generate-components +} declare module 'vue' { export type JSXComponent = { new (): ComponentPublicInstance } | FunctionalComponent + export interface GlobalComponents extends _GlobalComponents {} } declare module '@vue/runtime-dom' { @@ -28,6 +31,7 @@ declare module '@vue/runtime-dom' { export interface SVGAttributes { $children?: VNodeChild } + export interface GlobalComponents extends _GlobalComponents {} } declare module '@vue/runtime-core' { @@ -43,8 +47,5 @@ declare module '@vue/runtime-core' { export interface ComponentCustomProperties { $vuetify: Vuetify } - - export interface GlobalComponents { - // @generate-components - } + export interface GlobalComponents extends _GlobalComponents {} } From 1a0d9b7ecbde60cce31750c8f36b8e382c8c3535 Mon Sep 17 00:00:00 2001 From: Cintia Dewi <74539121+zentek-cintia-dewi@users.noreply.github.com> Date: Tue, 16 Jul 2024 20:59:55 +0200 Subject: [PATCH 12/22] fix(VDatePicker): programmatic start date with multiple range (#20169) fixes #20168 --- .../vuetify/src/components/VDatePicker/VDatePickerMonth.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx b/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx index 384f9b762bc..c23b3b8cfac 100644 --- a/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx +++ b/packages/vuetify/src/components/VDatePicker/VDatePickerMonth.tsx @@ -93,6 +93,9 @@ export const VDatePickerMonth = genericComponent()({ if (model.value.length === 0) { rangeStart.value = undefined + } else if (model.value.length === 1) { + rangeStart.value = model.value[0] + rangeStop.value = undefined } if (!rangeStart.value) { rangeStart.value = _value From f0fa4e6e705508699cb2013d322d8ce9b204a8d5 Mon Sep 17 00:00:00 2001 From: Glandos Date: Tue, 16 Jul 2024 21:06:49 +0200 Subject: [PATCH 13/22] docs(v-toolbar): fix example with image prop (#20000) --- .../docs/src/examples/v-toolbar/prop-floating-with-search.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/src/examples/v-toolbar/prop-floating-with-search.vue b/packages/docs/src/examples/v-toolbar/prop-floating-with-search.vue index 65299f8627f..a505b1a0e22 100644 --- a/packages/docs/src/examples/v-toolbar/prop-floating-with-search.vue +++ b/packages/docs/src/examples/v-toolbar/prop-floating-with-search.vue @@ -2,7 +2,7 @@ Date: Wed, 17 Jul 2024 02:18:49 +0700 Subject: [PATCH 14/22] fix(VDateInput): remove menu interaction when disabled/readonly (#20163) fixes #20147 Co-authored-by: John Leider --- packages/vuetify/src/labs/VDateInput/VDateInput.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/vuetify/src/labs/VDateInput/VDateInput.tsx b/packages/vuetify/src/labs/VDateInput/VDateInput.tsx index 5c30ea1b58a..92070c8c48d 100644 --- a/packages/vuetify/src/labs/VDateInput/VDateInput.tsx +++ b/packages/vuetify/src/labs/VDateInput/VDateInput.tsx @@ -71,6 +71,8 @@ export const VDateInput = genericComponent()({ return adapter.isValid(model.value) ? adapter.format(model.value, 'keyboardDate') : '' }) + const isInteractive = computed(() => !props.disabled && !props.readonly) + function onKeydown (e: KeyboardEvent) { if (e.key !== 'Enter') return @@ -105,12 +107,12 @@ export const VDateInput = genericComponent()({ Date: Wed, 17 Jul 2024 02:21:04 +0700 Subject: [PATCH 15/22] fix(VDateInput): inherit class / style props (#20002) fixes #19985 --- packages/vuetify/src/labs/VDateInput/VDateInput.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vuetify/src/labs/VDateInput/VDateInput.tsx b/packages/vuetify/src/labs/VDateInput/VDateInput.tsx index 92070c8c48d..e451bfc76e9 100644 --- a/packages/vuetify/src/labs/VDateInput/VDateInput.tsx +++ b/packages/vuetify/src/labs/VDateInput/VDateInput.tsx @@ -106,6 +106,8 @@ export const VDateInput = genericComponent()({ return ( Date: Wed, 17 Jul 2024 05:27:56 +1000 Subject: [PATCH 16/22] fix(VDataTable): use item.raw and columns in sorting transform (#20077) fixes #20045 --- packages/vuetify/src/components/VDataTable/VDataTable.tsx | 2 +- .../vuetify/src/components/VDataTable/VDataTableVirtual.tsx | 2 +- .../vuetify/src/components/VDataTable/composables/sort.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vuetify/src/components/VDataTable/VDataTable.tsx b/packages/vuetify/src/components/VDataTable/VDataTable.tsx index c98c3ba8147..7bc7b28a138 100644 --- a/packages/vuetify/src/components/VDataTable/VDataTable.tsx +++ b/packages/vuetify/src/components/VDataTable/VDataTable.tsx @@ -156,7 +156,7 @@ export const VDataTable = genericComponent( const { sortByWithGroups, opened, extractRows, isGroupOpen, toggleGroup } = provideGroupBy({ groupBy, sortBy, disableSort }) const { sortedItems } = useSortedItems(props, filteredItems, sortByWithGroups, { - transform: item => item.columns, + transform: item => ({ ...item.raw, ...item.columns }), sortFunctions, sortRawFunctions, }) diff --git a/packages/vuetify/src/components/VDataTable/VDataTableVirtual.tsx b/packages/vuetify/src/components/VDataTable/VDataTableVirtual.tsx index 898c15e06fc..b2714f7b0d7 100644 --- a/packages/vuetify/src/components/VDataTable/VDataTableVirtual.tsx +++ b/packages/vuetify/src/components/VDataTable/VDataTableVirtual.tsx @@ -110,7 +110,7 @@ export const VDataTableVirtual = genericComponent item.columns, + transform: item => ({ ...item.raw, ...item.columns }), sortFunctions, sortRawFunctions, }) diff --git a/packages/vuetify/src/components/VDataTable/composables/sort.ts b/packages/vuetify/src/components/VDataTable/composables/sort.ts index d2e316ddf96..03581dac81f 100644 --- a/packages/vuetify/src/components/VDataTable/composables/sort.ts +++ b/packages/vuetify/src/components/VDataTable/composables/sort.ts @@ -4,7 +4,7 @@ import { useProxiedModel } from '@/composables/proxiedModel' // Utilities import { computed, inject, provide, toRef } from 'vue' -import { isEmpty, propsFactory } from '@/util' +import { getObjectValueByPath, isEmpty, propsFactory } from '@/util' // Types import type { InjectionKey, PropType, Ref } from 'vue' @@ -148,8 +148,8 @@ export function sortItems ( if (sortOrder === false) continue - let sortA = a[1][sortKey] - let sortB = b[1][sortKey] + let sortA = getObjectValueByPath(a[1], sortKey) + let sortB = getObjectValueByPath(b[1], sortKey) let sortARaw = a[0].raw let sortBRaw = b[0].raw From 0f8fd5d2d82a45cefc47e64b1abcf6eba5e8b1f1 Mon Sep 17 00:00:00 2001 From: SonTT19 <49301480+SonTT19@users.noreply.github.com> Date: Wed, 17 Jul 2024 02:30:05 +0700 Subject: [PATCH 17/22] fix(VOtpInput): slice value with length on paste (#20164) fixes #20158 --- packages/vuetify/src/components/VOtpInput/VOtpInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx b/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx index f1b59a088df..fac9e739a33 100644 --- a/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx +++ b/packages/vuetify/src/components/VOtpInput/VOtpInput.tsx @@ -166,7 +166,7 @@ export const VOtpInput = genericComponent()({ e.preventDefault() e.stopPropagation() - const clipboardText = e?.clipboardData?.getData('Text') ?? '' + const clipboardText = e?.clipboardData?.getData('Text').slice(0, length.value) ?? '' if (isValidNumber(clipboardText)) return From d9e721173e66306123fb8f84a16833b3a8f18a38 Mon Sep 17 00:00:00 2001 From: Gabriel Batista <69487425+Gabriel3atista@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:35:12 -0300 Subject: [PATCH 18/22] docs(VList): add description for expanded and collapse-icon (#20156) Co-authored-by: John Leider --- packages/api-generator/src/locale/en/VList.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api-generator/src/locale/en/VList.json b/packages/api-generator/src/locale/en/VList.json index f2db19cf83b..c45480c4d31 100644 --- a/packages/api-generator/src/locale/en/VList.json +++ b/packages/api-generator/src/locale/en/VList.json @@ -7,6 +7,8 @@ "link": "Applies `v-list-item` hover styles. Useful when using the item is an _activator_.", "nav": "An alternative styling that reduces `v-list-item` width and rounds the corners. Typically used with **[v-navigation-drawer](/components/navigation-drawers)**.", "subheader": "Removes the top padding from `v-list-subheader` components. When used as a **String**, renders a subheader for you.", - "slim": "Reduces horizontal spacing for badges, icons, tooltips, and avatars within slim list items to create a more compact visual representation." + "slim": "Reduces horizontal spacing for badges, icons, tooltips, and avatars within slim list items to create a more compact visual representation.", + "collapseIcon": "Icon to display when the list item is expanded.", + "expandIcon": "Icon to display when the list item is collapsed." } } From 6bdd23051ecf87f364c9c60ce0ba23a447078d81 Mon Sep 17 00:00:00 2001 From: John Leider Date: Tue, 16 Jul 2024 15:13:21 -0500 Subject: [PATCH 19/22] Revert "fix(VNavigationDrawer): don't calculate transform width if temporary" This reverts commit be8babe86dc0872bcf774a13d1ede93f47635497. --- .../src/components/VNavigationDrawer/VNavigationDrawer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx index 1d45358649f..ef077b7c8da 100644 --- a/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx +++ b/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx @@ -177,7 +177,7 @@ export const VNavigationDrawer = genericComponent()({ return isDragging.value ? size * dragProgress.value : size }) - const elementSize = computed(() => ['top', 'bottom'].includes(props.location) || props.temporary ? 0 : width.value) + const elementSize = computed(() => ['top', 'bottom'].includes(props.location) ? 0 : width.value) const { layoutItemStyles, layoutItemScrimStyles, layoutIsReady } = useLayoutItem({ id: props.name, order: computed(() => parseInt(props.order, 10)), From 040fda8749630c08488aa1786f8a8b82e6b7779d Mon Sep 17 00:00:00 2001 From: John Leider Date: Tue, 16 Jul 2024 15:52:42 -0500 Subject: [PATCH 20/22] fix(VAppBar): scrollBehavior type for fully-hide --- packages/vuetify/src/components/VAppBar/VAppBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vuetify/src/components/VAppBar/VAppBar.tsx b/packages/vuetify/src/components/VAppBar/VAppBar.tsx index ec2b9b35708..87eb94ddb1e 100644 --- a/packages/vuetify/src/components/VAppBar/VAppBar.tsx +++ b/packages/vuetify/src/components/VAppBar/VAppBar.tsx @@ -20,7 +20,7 @@ import type { PropType } from 'vue' import type { VToolbarSlots } from '@/components/VToolbar/VToolbar' export const makeVAppBarProps = propsFactory({ - scrollBehavior: String as PropType<'hide' | 'inverted' | 'collapse' | 'elevate' | 'fade-image' | (string & {})>, + scrollBehavior: String as PropType<'hide' | 'fully-hide' | 'inverted' | 'collapse' | 'elevate' | 'fade-image' | (string & {})>, modelValue: { type: Boolean, default: true, From 67e888da4dee49e0656105871569c822cc1890c1 Mon Sep 17 00:00:00 2001 From: John Leider Date: Tue, 16 Jul 2024 15:52:58 -0500 Subject: [PATCH 21/22] docs(VAppBar): add fully-hide to list of available scrollBehavior options --- packages/docs/src/pages/en/components/app-bars.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/docs/src/pages/en/components/app-bars.md b/packages/docs/src/pages/en/components/app-bars.md index f7e37a97bff..b6e6b5fb50f 100644 --- a/packages/docs/src/pages/en/components/app-bars.md +++ b/packages/docs/src/pages/en/components/app-bars.md @@ -90,6 +90,7 @@ The `v-app-bar` component has a variety of props that allow you to customize its Available values: - **hide**: The default slot area will shift up and hide as the user scrolls down. The extension slot remains visible. +- **fully-hide**: The entire app bar will hide as the user scrolls down. - **collapse**: Shrink horizontally to a small bar in one corner. - **elevate**: Add a drop shadow to the app bar when scrolling. Ignores `scroll-threshold`, will always be applied with any amount of scrolling. - **fade-image**: Fade out the image as the user scrolls down. From 209dc01bae12278a831f78eccbce13ce0e1bfa3b Mon Sep 17 00:00:00 2001 From: John Leider Date: Tue, 16 Jul 2024 16:22:57 -0500 Subject: [PATCH 22/22] chore(release): publish v3.6.13 --- lerna.json | 2 +- packages/api-generator/package.json | 4 ++-- packages/docs/package.json | 6 +++--- packages/vuetify/package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lerna.json b/lerna.json index 5160690e94d..bded0c3bc47 100644 --- a/lerna.json +++ b/lerna.json @@ -13,6 +13,6 @@ } }, "npmClient": "yarn", - "version": "3.6.12", + "version": "3.6.13", "useWorkspaces": true } diff --git a/packages/api-generator/package.json b/packages/api-generator/package.json index 17b9d254fea..4d49de74ce7 100755 --- a/packages/api-generator/package.json +++ b/packages/api-generator/package.json @@ -1,6 +1,6 @@ { "name": "@vuetify/api-generator", - "version": "3.6.12", + "version": "3.6.13", "private": true, "description": "", "scripts": { @@ -17,7 +17,7 @@ "ts-morph": "^22.0.0", "tsx": "^4.7.2", "vue": "^3.4.27", - "vuetify": "^3.6.12" + "vuetify": "^3.6.13" }, "devDependencies": { "@types/stringify-object": "^4.0.5" diff --git a/packages/docs/package.json b/packages/docs/package.json index 5b327708613..30cdf5b349c 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 ", - "version": "3.6.12", + "version": "3.6.13", "repository": { "type": "git", "url": "git+https://github.com/vuetifyjs/vuetify.git", @@ -38,7 +38,7 @@ "vue-i18n": "^9.11.0", "vue-instantsearch": "^4.16.1", "vue-prism-component": "^2.0.0", - "vuetify": "^3.6.12" + "vuetify": "^3.6.13" }, "devDependencies": { "@emailjs/browser": "^4.3.3", @@ -50,7 +50,7 @@ "@vitejs/plugin-basic-ssl": "^1.1.0", "@vitejs/plugin-vue": "^5.0.4", "@vue/compiler-sfc": "^3.4.27", - "@vuetify/api-generator": "^3.6.12", + "@vuetify/api-generator": "^3.6.13", "ajv": "^8.12.0", "async-es": "^3.2.5", "date-fns": "^3.6.0", diff --git a/packages/vuetify/package.json b/packages/vuetify/package.json index 6d592ff9509..eaedd4194a0 100755 --- a/packages/vuetify/package.json +++ b/packages/vuetify/package.json @@ -1,7 +1,7 @@ { "name": "vuetify", "description": "Vue Material Component Framework", - "version": "3.6.12", + "version": "3.6.13", "author": { "name": "John Leider", "email": "john@vuetifyjs.com"