diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 68fe78ecd42..0702977ba2c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [johnleider, KaelWD, MajesticPotatoe] +github: [johnleider, KaelWD, MajesticPotatoe, yuwu9145] patreon: vuetify open_collective: vuetify ko_fi: # Replace with a single Ko-fi username diff --git a/lerna.json b/lerna.json index 56e9498822a..67a46ef4ea8 100644 --- a/lerna.json +++ b/lerna.json @@ -13,6 +13,6 @@ } }, "npmClient": "yarn", - "version": "3.6.9", + "version": "3.6.11", "useWorkspaces": true } diff --git a/packages/api-generator/package.json b/packages/api-generator/package.json index ee0d5f5f1d2..f1dd5e2429c 100755 --- a/packages/api-generator/package.json +++ b/packages/api-generator/package.json @@ -1,6 +1,6 @@ { "name": "@vuetify/api-generator", - "version": "3.6.9", + "version": "3.6.11", "private": true, "description": "", "scripts": { @@ -17,7 +17,7 @@ "ts-morph": "^22.0.0", "tsx": "^4.7.2", "vue": "^3.4.27", - "vuetify": "^3.6.9" + "vuetify": "^3.6.11" }, "devDependencies": { "@types/stringify-object": "^4.0.5" diff --git a/packages/api-generator/src/locale/en/VImg.json b/packages/api-generator/src/locale/en/VImg.json index 30af575e029..3519c534ae9 100644 --- a/packages/api-generator/src/locale/en/VImg.json +++ b/packages/api-generator/src/locale/en/VImg.json @@ -13,7 +13,7 @@ "src": "The image URL. This prop is mandatory.", "srcset": "A set of alternate images to use based on device size. [Read more...](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset).", "transition": "The transition to use when switching from `lazy-src` to `src`. Can be one of the [built in](/styles/transitions/) or custom transition.", - "gradient": "The gradient to apply to the image. This can be any valid CSS gradient declaration. You can find more information on the [MDN documentation for gradients](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient)." + "gradient": "The gradient to apply to the image. Only supports [linear-gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient) syntax, anything else should be done with classes." }, "slots": { "placeholder": "Display an overlay while the image is loading.", diff --git a/packages/docs/package.json b/packages/docs/package.json index b81806c85d6..85c9c8e6802 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.9", + "version": "3.6.11", "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.9" + "vuetify": "^3.6.11" }, "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.9", + "@vuetify/api-generator": "^3.6.11", "ajv": "^8.12.0", "async-es": "^3.2.5", "date-fns": "^3.6.0", diff --git a/packages/docs/src/data/team.json b/packages/docs/src/data/team.json index e3e9f304b8f..2c7625a05cc 100644 --- a/packages/docs/src/data/team.json +++ b/packages/docs/src/data/team.json @@ -251,21 +251,5 @@ "name": "Yoones Khoshghadam", "team": "legends", "joined": "January 2023" - }, - "alexandriajackson": { - "avatar": "https://cdn.vuetifyjs.com/docs/images/team/alexandria.jpg", - "discord": "alexandriajackson_", - "focus": [ - "[vuetifyjs/*](https://github.com/vuetifyjs)" - ], - "languages": [ - "English" - ], - "location": "Dallas, TX, USA", - "name": "Alexandria Jackson", - "team": "company", - "twitter": "AlexxJackson96", - "work": "Marketing Coordinator @ Vuetify", - "joined": "Sept 2023" } } diff --git a/packages/docs/src/examples/v-data-table/headers-multiple.vue b/packages/docs/src/examples/v-data-table/headers-multiple.vue index 1a818f16bf3..e773cf9e5b3 100644 --- a/packages/docs/src/examples/v-data-table/headers-multiple.vue +++ b/packages/docs/src/examples/v-data-table/headers-multiple.vue @@ -84,7 +84,7 @@ ], }, ], - desserts: [ + items: [ { name: 'Great Pyramid of Giza', location: 'Egypt', diff --git a/packages/docs/src/examples/v-data-table/prop-dense.vue b/packages/docs/src/examples/v-data-table/prop-dense.vue index c8788a3c365..78f2000c3c3 100644 --- a/packages/docs/src/examples/v-data-table/prop-dense.vue +++ b/packages/docs/src/examples/v-data-table/prop-dense.vue @@ -92,84 +92,86 @@ diff --git a/packages/docs/src/pages/en/components/images.md b/packages/docs/src/pages/en/components/images.md index 8db99a88a15..5f7c6951cfe 100644 --- a/packages/docs/src/pages/en/components/images.md +++ b/packages/docs/src/pages/en/components/images.md @@ -59,6 +59,12 @@ If the provided aspect ratio doesn't match that of the actual image, the default +#### Gradient + +The `gradient` prop can be used to apply a simple gradient overlay to the image. More complex gradients should be written as a class on the content slot instead. + + + ### Slots #### Placeholder diff --git a/packages/docs/src/pages/en/features/treeshaking.md b/packages/docs/src/pages/en/features/treeshaking.md index 85d722053d1..c3ed964f47b 100644 --- a/packages/docs/src/pages/en/features/treeshaking.md +++ b/packages/docs/src/pages/en/features/treeshaking.md @@ -179,7 +179,7 @@ Dynamic components using `` can be registered locally: import { VChip } from 'vuetify/components/VChip' import { shallowRef } from 'vue' - const btn = shallowRef(false) + const button = shallowRef(false) ``` diff --git a/packages/vuetify/package.json b/packages/vuetify/package.json index e8f3533dda8..fb52aa72168 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.9", + "version": "3.6.11", "author": { "name": "John Leider", "email": "john@vuetifyjs.com" diff --git a/packages/vuetify/src/components/VDatePicker/VDatePicker.tsx b/packages/vuetify/src/components/VDatePicker/VDatePicker.tsx index 55c2e0c5b72..0e6e6d98487 100644 --- a/packages/vuetify/src/components/VDatePicker/VDatePicker.tsx +++ b/packages/vuetify/src/components/VDatePicker/VDatePicker.tsx @@ -230,8 +230,13 @@ export const VDatePicker = genericComponent { - const before = adapter.date(wrapInArray(oldVal)[oldVal.length - 1]) - const after = adapter.date(wrapInArray(val)[val.length - 1]) + const arrBefore = wrapInArray(oldVal) + const arrAfter = wrapInArray(val) + + if (!arrAfter.length) return + + const before = adapter.date(arrBefore[arrBefore.length - 1]) + const after = adapter.date(arrAfter[arrAfter.length - 1]) const newMonth = adapter.getMonth(after) const newYear = adapter.getYear(after) diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index 97e6460a56b..70cedce4870 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -156,7 +156,7 @@ export const VOverlay = genericComponent()({ } = useActivator(props, { isActive, isTop: localTop }) const potentialShadowDomRoot = computed(() => (activatorEl?.value as Element)?.getRootNode() as Element) const { teleportTarget } = useTeleport(computed(() => props.attach || props.contained || - potentialShadowDomRoot.value instanceof ShadowRoot ? potentialShadowDomRoot.value : false)) + potentialShadowDomRoot.value instanceof ShadowRoot ? potentialShadowDomRoot.value ?? true : false)) const { dimensionStyles } = useDimension(props) const isMounted = useHydration() const { scopeId } = useScopeId() diff --git a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx index c0acfe3f85f..23f6f241263 100644 --- a/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx +++ b/packages/vuetify/src/labs/VNumberInput/VNumberInput.tsx @@ -97,6 +97,11 @@ export const VNumberInput = genericComponent()({ return props.hideInput ? 'stacked' : props.controlVariant }) + const incrementIcon = computed(() => controlVariant.value === 'split' ? '$plus' : '$collapse') + const decrementIcon = computed(() => controlVariant.value === 'split' ? '$minus' : '$expand') + const controlNodeSize = computed(() => controlVariant.value === 'split' ? 'default' : 'small') + const controlNodeDefaultHeight = computed(() => controlVariant.value === 'stacked' ? 'auto' : '100%') + const incrementSlotProps = computed(() => ({ click: onClickUp })) const decrementSlotProps = computed(() => ({ click: onClickDown })) @@ -160,77 +165,80 @@ export const VNumberInput = genericComponent()({ useRender(() => { const { modelValue: _, ...textFieldProps } = VTextField.filterProps(props) + function incrementControlNode () { + return !slots.increment ? ( + + ) : ( + + { slots.increment(incrementSlotProps.value) } + + ) + } + + function decrementControlNode () { + return !slots.decrement ? ( + + ) : ( + + { slots.decrement(decrementSlotProps.value) } + + ) + } + function controlNode () { - const defaultHeight = controlVariant.value === 'stacked' ? 'auto' : '100%' return (
- { - !slots.decrement ? ( - - ) : ( - - { slots.decrement(decrementSlotProps.value) } - - ) - } + { decrementControlNode() } - { - !slots.increment ? ( - - ) : ( - - { slots.increment(incrementSlotProps.value) } - - ) - } + { incrementControlNode() }
) } @@ -245,15 +253,7 @@ export const VNumberInput = genericComponent()({
- + { incrementControlNode() }
) : (!props.reverse ? <>{ dividerNode() }{ controlNode() } @@ -265,15 +265,7 @@ export const VNumberInput = genericComponent()({ controlVariant.value === 'split' ? (
- + { decrementControlNode() }
diff --git a/packages/vuetify/src/labs/VStepperVertical/VStepperVertical.tsx b/packages/vuetify/src/labs/VStepperVertical/VStepperVertical.tsx index 2cd11d98ae4..ba3ee62ef8a 100644 --- a/packages/vuetify/src/labs/VStepperVertical/VStepperVertical.tsx +++ b/packages/vuetify/src/labs/VStepperVertical/VStepperVertical.tsx @@ -57,7 +57,7 @@ export const VStepperVertical = genericComponent()({ setup (props, { slots }) { const vExpansionPanelsRef = ref() - const { color, editable, prevText, nextText, hideActions } = toRefs(props) + const { color, eager, editable, prevText, nextText, hideActions } = toRefs(props) const model = useProxiedModel(props, 'modelValue') const items = computed(() => props.items.map((item, index) => { @@ -74,6 +74,7 @@ export const VStepperVertical = genericComponent()({ provideDefaults({ VStepperVerticalItem: { color, + eager, editable, prevText, nextText, diff --git a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx index 7778c69df26..23541031c0c 100644 --- a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx +++ b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx @@ -8,11 +8,10 @@ import { makeFilterProps, useFilter } from '@/composables/filter' import { useProxiedModel } from '@/composables/proxiedModel' // Utilities -import { computed, provide, ref, toRef, watch } from 'vue' -import { genericComponent, getCurrentInstance, omit, propsFactory, useRender } from '@/util' +import { computed, provide, ref, toRef } from 'vue' +import { genericComponent, omit, propsFactory, useRender } from '@/util' // Types -import type { ExtractPublicPropTypes } from 'vue' import { VTreeviewSymbol } from './shared' import type { VListChildrenSlots } from '@/components/VList/VListChildren' import type { ListItem } from '@/composables/list-items' @@ -35,8 +34,6 @@ export const makeVTreeviewProps = propsFactory({ ...omit(makeVListProps({ collapseIcon: '$treeviewCollapse', expandIcon: '$treeviewExpand', - selectStrategy: 'classic' as const, - openStrategy: 'multiple' as const, slim: true, }), ['nav']), }, 'VTreeview') @@ -60,17 +57,16 @@ export const VTreeview = genericComponent( }, setup (props, { slots }) { - const vm = getCurrentInstance('VTreeview') const { items } = useListItems(props) const activeColor = toRef(props, 'activeColor') const baseColor = toRef(props, 'baseColor') const color = toRef(props, 'color') - const opened = useProxiedModel(props, 'opened') const activated = useProxiedModel(props, 'activated') const selected = useProxiedModel(props, 'selected') const vListRef = ref() + const opened = computed(() => props.openAll ? openAll(items.value) : props.opened) const flatItems = computed(() => flatten(items.value)) const search = toRef(props, 'search') const { filteredItems } = useFilter(props, flatItems, search) @@ -105,10 +101,6 @@ export const VTreeview = genericComponent( return arr } - watch(() => props.openAll, val => { - opened.value = val ? openAll(items.value) : [] - }, { immediate: true }) - function openAll (item: any) { let ids: number[] = [] @@ -148,7 +140,7 @@ export const VTreeview = genericComponent( }) useRender(() => { - const listProps = VList.filterProps(vm.vnode.props! as ExtractPublicPropTypes) + const listProps = VList.filterProps(props) const treeviewChildrenProps = VTreeviewChildren.filterProps(props) @@ -161,6 +153,7 @@ export const VTreeview = genericComponent( props.class, ]} style={ props.style } + opened={ opened.value } v-model:activated={ activated.value } v-model:selected={ selected.value } > diff --git a/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx b/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx new file mode 100644 index 00000000000..53d114a8fab --- /dev/null +++ b/packages/vuetify/src/labs/VTreeview/__tests__/VTreeview.spec.cy.tsx @@ -0,0 +1,309 @@ +/// + +// Components +import { VTreeview } from '../VTreeview' + +// Utilities +import { ref } from 'vue' + +describe('VTreeview', () => { + const items = ref([ + { + id: 1, + title: 'Videos :', + children: [ + { + id: 2, + title: 'Tutorials :', + children: [ + { id: 3, title: 'Basic layouts : mp4' }, + { id: 4, title: 'Advanced techniques : mp4' }, + { id: 5, title: 'All about app : dir' }, + ], + }, + { id: 6, title: 'Intro : mov' }, + { id: 7, title: 'Conference introduction : avi' }, + ], + }, + ]) + describe('activate', () => { + it('single-leaf strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value).to.deep.equal([5]) + }) + }) + it('leaf strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value.sort()).to.deep.equal([3, 4, 5].sort()) + }) + }) + it('independent strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([1]) + }) + .get('.v-treeview-item').eq(1).click() + .then(_ => { + expect(activated.value).to.deep.equal([1, 2]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value.sort()).to.deep.equal([1, 2, 3, 4, 5].sort()) + }) + }) + it('single-independent strategy', () => { + const activated = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-treeview-item').should('have.length', 7) + .get('.v-treeview-item').eq(0).click() + .then(_ => { + expect(activated.value).to.deep.equal([1]) + }) + .get('.v-treeview-item').eq(1).click() + .then(_ => { + expect(activated.value).to.deep.equal([2]) + }) + .get('.v-treeview-item').eq(2).click() + .get('.v-treeview-item').eq(3).click() + .get('.v-treeview-item').eq(4).click() + .then(_ => { + expect(activated.value).to.deep.equal([5]) + }) + }) + }) + describe('select', () => { + it('single-leaf strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 5) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([4]) + }) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([6]) + }) + }) + it('leaf strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 5) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4].sort()) + }) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4, 6].sort()) + }) + }) + it('independent strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 7) + .get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4].sort()) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([1, 2, 3, 4].sort()) + }) + }) + it('single-independent strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').should('have.length', 7) + .get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([4].sort()) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([1].sort()) + }) + }) + it('classic strategy', () => { + const selected = ref([]) + cy.mount(() => ( + <> + + + )) + + cy.get('.v-checkbox-btn').eq(2).click(20, 20) + .get('.v-checkbox-btn').eq(3).click(20, 20) + .get('.v-checkbox-btn').eq(4).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([3, 4, 5]) + }) + .get('.v-checkbox-btn').eq(1).click(20, 20) + .then(_ => { + expect(selected.value).to.deep.equal([]) + }) + .get('.v-checkbox-btn').eq(0).click(20, 20) + .then(_ => { + expect(selected.value.sort()).to.deep.equal([3, 4, 5, 6, 7].sort()) + }) + }) + }) + it('should have all items visible when open-all is applied', () => { + cy.mount(() => ( + <> + + + )) + .get('.v-treeview-item') + .filter(':visible') + .should('have.length', 7) + }) +})