From a02bec2b74ed2d38492ed9bcc9b376ed748d5864 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 12 May 2020 01:51:45 -0300 Subject: [PATCH 01/41] chore(docs): hotfix for collapse accordion docs --- src/components/collapse/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/collapse/README.md b/src/components/collapse/README.md index 3c88e4b6853..e737984e9d6 100644 --- a/src/components/collapse/README.md +++ b/src/components/collapse/README.md @@ -146,7 +146,7 @@ at a time.
- Accordion 1 + Accordion 1 @@ -158,7 +158,7 @@ at a time. - Accordion 2 + Accordion 2 @@ -169,7 +169,7 @@ at a time. - Accordion 3 + Accordion 3 From 41680ba0067b784f14e272ad5c4993f6f7f46f14 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Tue, 12 May 2020 03:13:39 -0300 Subject: [PATCH 02/41] chore(docs): fix collapse accordion example to not use `href="#"` on buttons (closes #5352) (#5353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(docs): fix collapse accordion example to not use `href="#"` on buttons * Update README.md * Update README.md * Update README.md Co-authored-by: Jacob Müller --- src/components/collapse/README.md | 6 +++--- src/directives/toggle/README.md | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/collapse/README.md b/src/components/collapse/README.md index 3c88e4b6853..e737984e9d6 100644 --- a/src/components/collapse/README.md +++ b/src/components/collapse/README.md @@ -146,7 +146,7 @@ at a time.
- Accordion 1 + Accordion 1 @@ -158,7 +158,7 @@ at a time. - Accordion 2 + Accordion 2 @@ -169,7 +169,7 @@ at a time. - Accordion 3 + Accordion 3 diff --git a/src/directives/toggle/README.md b/src/directives/toggle/README.md index c1df03a139f..b5deebfefe2 100644 --- a/src/directives/toggle/README.md +++ b/src/directives/toggle/README.md @@ -90,6 +90,19 @@ trigger element when the target component is closed, and removed when open. As o } ``` +## Preventing the target from opening or closing + +To prevent the trigger element from toggling the target, set the `disabled` prop on `' - }) - const wrapper = mount(App, { - localVue - }) + } + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') @@ -39,10 +35,8 @@ describe('v-b-modal directive', () => { }) it('works on links', async () => { - const localVue = new CreateLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bModal: VBModal }, @@ -58,10 +52,8 @@ describe('v-b-modal directive', () => { this.$root.$off(EVENT_SHOW, spy) }, template: '{{ text }}' - }) - const wrapper = mount(App, { - localVue - }) + } + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('A') @@ -81,10 +73,8 @@ describe('v-b-modal directive', () => { }) it('works on non-buttons', async () => { - const localVue = new CreateLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bModal: VBModal }, @@ -100,10 +90,8 @@ describe('v-b-modal directive', () => { this.$root.$off(EVENT_SHOW, spy) }, template: '{{ text }}' - }) - const wrapper = mount(App, { - localVue - }) + } + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('SPAN') @@ -129,10 +117,8 @@ describe('v-b-modal directive', () => { }) it('works on non-buttons using keydown space', async () => { - const localVue = new CreateLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bModal: VBModal }, @@ -148,10 +134,8 @@ describe('v-b-modal directive', () => { this.$root.$off(EVENT_SHOW, spy) }, template: '{{ text }}' - }) - const wrapper = mount(App, { - localVue - }) + } + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('SPAN') @@ -170,10 +154,8 @@ describe('v-b-modal directive', () => { }) it('works on non-buttons using keydown enter', async () => { - const localVue = new CreateLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bModal: VBModal }, @@ -189,10 +171,8 @@ describe('v-b-modal directive', () => { this.$root.$off(EVENT_SHOW, spy) }, template: '{{ text }}' - }) - const wrapper = mount(App, { - localVue - }) + } + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('SPAN') diff --git a/src/directives/popover/popover.spec.js b/src/directives/popover/popover.spec.js index d534e0c3fbe..9e22a494acc 100644 --- a/src/directives/popover/popover.spec.js +++ b/src/directives/popover/popover.spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { createContainer, waitNT, waitRAF } from '../../../tests/utils' import { VBPopover } from './popover' import { BVPopover } from '../../components/popover/helpers/bv-popover' @@ -43,17 +43,15 @@ describe('v-b-popover directive', () => { it('should have BVPopover Vue instance', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bPopover: VBPopover }, template: `` - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) @@ -80,17 +78,15 @@ describe('v-b-popover directive', () => { it('should work', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bPopover: VBPopover }, template: `` - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) diff --git a/src/directives/toggle/toggle.spec.js b/src/directives/toggle/toggle.spec.js index 6aafa17b7f8..4b5e716228b 100644 --- a/src/directives/toggle/toggle.spec.js +++ b/src/directives/toggle/toggle.spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { waitNT } from '../../../tests/utils' import { VBToggle } from './toggle' @@ -13,10 +13,8 @@ const EVENT_STATE_SYNC = 'bv::collapse::sync::state' describe('v-b-toggle directive', () => { it('works on buttons', async () => { - const localVue = createLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, @@ -27,11 +25,9 @@ describe('v-b-toggle directive', () => { this.$root.$off(EVENT_TOGGLE, spy) }, template: '' - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') @@ -58,10 +54,8 @@ describe('v-b-toggle directive', () => { }) it('works on passing ID as directive value', async () => { - const localVue = createLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, @@ -72,11 +66,9 @@ describe('v-b-toggle directive', () => { this.$root.$off(EVENT_TOGGLE, spy) }, template: `` - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') @@ -101,10 +93,8 @@ describe('v-b-toggle directive', () => { }) it('works on passing ID as directive argument', async () => { - const localVue = createLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, @@ -115,11 +105,9 @@ describe('v-b-toggle directive', () => { this.$root.$off(EVENT_TOGGLE, spy) }, template: `` - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') @@ -144,10 +132,8 @@ describe('v-b-toggle directive', () => { }) it('works with multiple targets, and updates when targets change', async () => { - const localVue = createLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, @@ -164,13 +150,12 @@ describe('v-b-toggle directive', () => { this.$root.$off(EVENT_TOGGLE, spy) }, template: `` - }) + } const wrapper = mount(App, { propsData: { target: 'test1' - }, - localVue + } }) expect(wrapper.vm).toBeDefined() @@ -231,10 +216,8 @@ describe('v-b-toggle directive', () => { }) it('works on non-buttons', async () => { - const localVue = createLocalVue() const spy = jest.fn() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, @@ -250,11 +233,9 @@ describe('v-b-toggle directive', () => { this.$root.$off(EVENT_TOGGLE, spy) }, template: '{{ text }}' - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('SPAN') @@ -324,18 +305,14 @@ describe('v-b-toggle directive', () => { }) it('responds to state update events', async () => { - const localVue = createLocalVue() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, template: '' - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') @@ -374,18 +351,14 @@ describe('v-b-toggle directive', () => { }) it('responds to private sync state update events', async () => { - const localVue = createLocalVue() - - const App = localVue.extend({ + const App = { directives: { bToggle: VBToggle }, template: '' - }) + } - const wrapper = mount(App, { - localVue - }) + const wrapper = mount(App) expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('BUTTON') diff --git a/src/directives/tooltip/tooltip.spec.js b/src/directives/tooltip/tooltip.spec.js index 28193a31bbc..4a6a9b381dc 100644 --- a/src/directives/tooltip/tooltip.spec.js +++ b/src/directives/tooltip/tooltip.spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { createContainer, waitNT, waitRAF } from '../../../tests/utils' import { VBTooltip } from './tooltip' import { BVTooltip } from '../../components/tooltip/helpers/bv-tooltip' @@ -11,10 +11,10 @@ describe('v-b-tooltip directive', () => { const origGetBCR = Element.prototype.getBoundingClientRect beforeEach(() => { + // Hack to make Popper not bork out during tests + // Note: Popper still does not do any positioning calculation in JSDOM though + // So we cannot test actual positioning - just detect when it is open // https://github.com/FezVrasta/popper.js/issues/478#issuecomment-407422016 - // Hack to make Popper not bork out during tests. - // Note popper still does not do any positioning calculation in JSDOM though. - // So we cannot test actual positioning... just detect when it is open. document.createRange = () => ({ setStart: () => {}, setEnd: () => {}, @@ -43,17 +43,15 @@ describe('v-b-tooltip directive', () => { it('should have BVTooltip Vue class instance', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bTooltip: VBTooltip }, template: '' - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) @@ -80,17 +78,15 @@ describe('v-b-tooltip directive', () => { it('should work', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bTooltip: VBTooltip }, template: '' - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) @@ -134,17 +130,15 @@ describe('v-b-tooltip directive', () => { it('should not show tooltip when title is empty', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bTooltip: VBTooltip }, template: '' - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) @@ -183,17 +177,15 @@ describe('v-b-tooltip directive', () => { it('variant and customClass should work', async () => { jest.useFakeTimers() - const localVue = new CreateLocalVue() - const App = localVue.extend({ + const App = { directives: { bTooltip: VBTooltip }, template: `` - }) + } const wrapper = mount(App, { - localVue, attachTo: createContainer() }) diff --git a/src/icons/icons.spec.js b/src/icons/icons.spec.js index e2af4f7d4fb..a1e75328cfa 100644 --- a/src/icons/icons.spec.js +++ b/src/icons/icons.spec.js @@ -1,31 +1,15 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { createLocalVue, mount } from '@vue/test-utils' import { IconsPlugin } from './index' import { BIcon } from './icon' import { makeIcon } from './helpers/make-icon' -describe('icons', () => { - const localVue = new CreateLocalVue() - - const parentComponent = { - name: 'ParentComponent', - components: { - // For testing user defined Icons - BIconFakeIconTest: makeIcon('FakeIconTest', '') - }, - render(h) { - return h(this.$slots.default) - } - } - - beforeAll(() => { - // We install all icon components so that BIcon will work - localVue.use(IconsPlugin) - }) +const localVue = createLocalVue() +localVue.use(IconsPlugin) +describe('icons', () => { it('b-icon has expected structure', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill' } @@ -57,7 +41,6 @@ describe('icons', () => { it('b-icon has expected structure when `stacked` prop is true', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', stacked: true @@ -117,7 +100,6 @@ describe('icons', () => { // As we currently do not check the validity of icon names const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: undefined } @@ -136,7 +118,6 @@ describe('icons', () => { it('b-icon with unknown icon name renders BIconBlank', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'unknown-icon-name' } @@ -158,7 +139,6 @@ describe('icons', () => { it('b-icon variant works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', variant: 'danger' @@ -185,7 +165,6 @@ describe('icons', () => { it('b-icon font-scale prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', fontScale: '1.25' @@ -211,10 +190,21 @@ describe('icons', () => { }) it('b-icon with custom icon works', async () => { + const ParentComponent = { + name: 'ParentComponent', + components: { + // For testing user defined Icons + BIconFakeIconTest: makeIcon('FakeIconTest', '') + }, + render(h) { + return h(this.$slots.default) + } + } + const wrapper = mount(BIcon, { localVue, // Parent component has a custom icon registered - parentComponent: parentComponent, + parentComponent: ParentComponent, propsData: { icon: 'fake-icon-test' } @@ -236,7 +226,6 @@ describe('icons', () => { it('b-icon rotate prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', rotate: '45' @@ -262,7 +251,6 @@ describe('icons', () => { it('b-icon scale prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', scale: '1.5' @@ -288,7 +276,6 @@ describe('icons', () => { it('b-icon flip-h prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', flipH: true @@ -314,7 +301,6 @@ describe('icons', () => { it('b-icon flip-v prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', flipV: true @@ -340,7 +326,6 @@ describe('icons', () => { it('b-icon flip-h prop works with flip-v prop', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', flipH: true, @@ -367,7 +352,6 @@ describe('icons', () => { it('b-icon scale prop works with flip-h prop', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', scale: '1.5', @@ -394,7 +378,6 @@ describe('icons', () => { it('b-icon scale prop works with flip-v prop', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', scale: '1.5', @@ -421,7 +404,6 @@ describe('icons', () => { it('b-icon scale prop works with flip-h and flip-v prop', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', scale: '1.5', @@ -449,7 +431,6 @@ describe('icons', () => { it('b-icon shift-h and shift-v props work', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', shiftH: 8, @@ -476,7 +457,6 @@ describe('icons', () => { it('b-icon shift-h and shift-v props work with rotate prop', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'alarm-fill', rotate: 45, @@ -507,7 +487,6 @@ describe('icons', () => { it('b-icon animation prop works', async () => { const wrapper = mount(BIcon, { localVue, - parentComponent: parentComponent, propsData: { icon: 'circle-fill', animation: 'spin' diff --git a/src/mixins/click-out.spec.js b/src/mixins/click-out.spec.js index 4dab3faa9f6..6c1fe677c20 100644 --- a/src/mixins/click-out.spec.js +++ b/src/mixins/click-out.spec.js @@ -1,12 +1,12 @@ -import Vue from 'vue' -import { mount } from '@vue/test-utils' +import { createLocalVue, mount } from '@vue/test-utils' import { createContainer, waitNT } from '../../tests/utils' import clickOutMixin from './click-out' describe('utils/click-out', () => { it('works', async () => { let count = 0 - const App = Vue.extend({ + const localVue = createLocalVue() + const App = localVue.extend({ mixins: [clickOutMixin], // `listenForClickOut` comes from the mixin data created() { @@ -23,7 +23,8 @@ describe('utils/click-out', () => { }) const wrapper = mount(App, { - attachTo: createContainer() + attachTo: createContainer(), + localVue }) const clickEvt = new MouseEvent('click') diff --git a/src/mixins/focus-in.spec.js b/src/mixins/focus-in.spec.js index 2599232492b..267f45da35b 100644 --- a/src/mixins/focus-in.spec.js +++ b/src/mixins/focus-in.spec.js @@ -1,12 +1,12 @@ -import Vue from 'vue' -import { mount } from '@vue/test-utils' +import { createLocalVue, mount } from '@vue/test-utils' import { createContainer, waitNT } from '../../tests/utils' import focusInMixin from './focus-in' describe('utils/focus-in', () => { it('works', async () => { let count = 0 - const App = Vue.extend({ + const localVue = createLocalVue() + const App = localVue.extend({ mixins: [focusInMixin], // listenForFocusIn comes from the mixin created() { @@ -23,7 +23,8 @@ describe('utils/focus-in', () => { }) const wrapper = mount(App, { - attachTo: createContainer() + attachTo: createContainer(), + localVue }) const focusinEvt = new FocusEvent('focusin') diff --git a/src/mixins/listen-on-document.spec.js b/src/mixins/listen-on-document.spec.js index cbd8eb07916..6e7c2e322ea 100644 --- a/src/mixins/listen-on-document.spec.js +++ b/src/mixins/listen-on-document.spec.js @@ -1,16 +1,14 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { createContainer } from '../../tests/utils' import listenOnDocumentMixin from './listen-on-document' describe('mixins/listen-on-document', () => { - const localVue = new CreateLocalVue() - it('works', async () => { const spyClick1 = jest.fn() const spyClick2 = jest.fn() const spyFocusin = jest.fn() - const TestComponent = localVue.extend({ + const TestComponent = { mixins: [listenOnDocumentMixin], props: { offClickOne: { @@ -33,9 +31,9 @@ describe('mixins/listen-on-document', () => { render(h) { return h('div', this.$slots.default) } - }) + } - const App = localVue.extend({ + const App = { components: { TestComponent }, props: { offClickOne: { @@ -57,7 +55,7 @@ describe('mixins/listen-on-document', () => { this.destroy ? h() : h(TestComponent, { props }, 'test-component') ]) } - }) + } const wrapper = mount(App, { attachTo: createContainer(), diff --git a/src/mixins/listen-on-root.spec.js b/src/mixins/listen-on-root.spec.js index af366c870a4..f5f149edc3e 100644 --- a/src/mixins/listen-on-root.spec.js +++ b/src/mixins/listen-on-root.spec.js @@ -1,13 +1,12 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import listenOnRootMixin from './listen-on-root' describe('mixins/listen-on-root', () => { - const localVue = new CreateLocalVue() it('works', async () => { const spyOn = jest.fn() const spyOnce = jest.fn() - const TestComponent = localVue.extend({ + const TestComponent = { mixins: [listenOnRootMixin], created() { this.listenOnRoot('root-on', spyOn) @@ -16,9 +15,9 @@ describe('mixins/listen-on-root', () => { render(h) { return h('div', this.$slots.default) } - }) + } - const App = localVue.extend({ + const App = { components: { TestComponent }, props: { destroy: { @@ -29,10 +28,9 @@ describe('mixins/listen-on-root', () => { render(h) { return h('div', [this.destroy ? h() : h(TestComponent, 'test-component')]) } - }) + } const wrapper = mount(App, { - localVue, propsData: { destroy: false } @@ -47,22 +45,18 @@ describe('mixins/listen-on-root', () => { const $root = wrapper.vm.$root $root.$emit('root-on') - expect(spyOn).toHaveBeenCalledTimes(1) expect(spyOnce).not.toHaveBeenCalled() await wrapper.setProps({ destroy: true }) - expect(spyOn).toHaveBeenCalledTimes(1) expect(spyOnce).not.toHaveBeenCalled() $root.$emit('root-on') - expect(spyOn).toHaveBeenCalledTimes(1) expect(spyOnce).not.toHaveBeenCalled() $root.$emit('root-once') - expect(spyOn).toHaveBeenCalledTimes(1) expect(spyOnce).not.toHaveBeenCalled() diff --git a/src/mixins/listen-on-window.spec.js b/src/mixins/listen-on-window.spec.js index 4461abbb8c5..4289a296c9a 100644 --- a/src/mixins/listen-on-window.spec.js +++ b/src/mixins/listen-on-window.spec.js @@ -1,16 +1,14 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { createContainer } from '../../tests/utils' import listenOnWindowMixin from './listen-on-window' describe('mixins/listen-on-window', () => { - const localVue = new CreateLocalVue() - it('works', async () => { const spyResize1 = jest.fn() const spyResize2 = jest.fn() const spyScroll = jest.fn() - const TestComponent = localVue.extend({ + const TestComponent = { mixins: [listenOnWindowMixin], props: { offResizeOne: { @@ -33,9 +31,9 @@ describe('mixins/listen-on-window', () => { render(h) { return h('div', this.$slots.default) } - }) + } - const App = localVue.extend({ + const App = { components: { TestComponent }, props: { offResizeOne: { @@ -53,7 +51,7 @@ describe('mixins/listen-on-window', () => { } return h('div', [this.destroy ? h() : h(TestComponent, { props }, 'test-component')]) } - }) + } const wrapper = mount(App, { attachTo: createContainer(), diff --git a/src/utils/config.spec.js b/src/utils/config.spec.js index 420179ea58d..ff096338848 100644 --- a/src/utils/config.spec.js +++ b/src/utils/config.spec.js @@ -104,10 +104,10 @@ describe('utils/config', () => { }) it('config via Vue.use(BootstrapVue) works', async () => { + const localVue = createLocalVue() const testConfig = { BAlert: { variant: 'foobar' } } - const localVue = createLocalVue() expect(getConfig()).toEqual({}) @@ -120,10 +120,10 @@ describe('utils/config', () => { }) it('config via Vue.use(ComponentPlugin) works', async () => { + const localVue = createLocalVue() const testConfig = { BAlert: { variant: 'foobar' } } - const localVue = createLocalVue() expect(getConfig()).toEqual({}) @@ -136,10 +136,10 @@ describe('utils/config', () => { }) it('config via Vue.use(BVConfig) works', async () => { + const localVue = createLocalVue() const testConfig = { BAlert: { variant: 'foobar' } } - const localVue = createLocalVue() expect(getConfig()).toEqual({}) diff --git a/src/utils/dom.spec.js b/src/utils/dom.spec.js index 08ebc01884e..34397d6ec97 100644 --- a/src/utils/dom.spec.js +++ b/src/utils/dom.spec.js @@ -1,4 +1,3 @@ -import Vue from 'vue' import { mount } from '@vue/test-utils' import { createContainer } from '../../tests/utils' import { @@ -26,8 +25,7 @@ const template = `
` - -const App = Vue.extend({ template }) +const App = { template } describe('utils/dom', () => { it('isElement() works', async () => { diff --git a/src/utils/transporter.spec.js b/src/utils/transporter.spec.js index 475586d6cf7..9a7b8809913 100644 --- a/src/utils/transporter.spec.js +++ b/src/utils/transporter.spec.js @@ -1,20 +1,17 @@ -import { mount, createLocalVue as CreateLocalVue } from '@vue/test-utils' +import { mount } from '@vue/test-utils' import { createContainer, waitNT } from '../../tests/utils' import { BTransporterSingle } from './transporter' describe('utils/transporter component', () => { - const localVue = new CreateLocalVue() - it('renders in-pace when disabled=true', async () => { - const App = localVue.extend({ + const App = { render(h) { return h(BTransporterSingle, { props: { disabled: true } }, [h('div', 'content')]) } - }) + } const wrapper = mount(App, { - attachTo: createContainer(), - localVue + attachTo: createContainer() }) expect(wrapper.vm).toBeDefined() @@ -25,17 +22,16 @@ describe('utils/transporter component', () => { }) it('does not render in-pace when disabled=false', async () => { - const App = localVue.extend({ + const App = { render(h) { return h(BTransporterSingle, { props: { disabled: false } }, [ h('div', { attrs: { id: 'foobar' } }, 'content') ]) } - }) + } const wrapper = mount(App, { - attachTo: createContainer(), - localVue + attachTo: createContainer() }) expect(wrapper.vm).toBeDefined() diff --git a/tests/setup.js b/tests/setup.js index a526836d70c..f7810e142c4 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,6 +1,6 @@ import '@testing-library/jest-dom' import { config as vtuConfig } from '@vue/test-utils' -import { TransitionGroupStub, TransitionStub } from './components' -vtuConfig.stubs['transition-group'] = TransitionGroupStub -vtuConfig.stubs.transition = TransitionStub +// Don't stub `` and `` components +vtuConfig.stubs.transition = false +vtuConfig.stubs['transition-group'] = false From 064cdf4f7e7c6b779c1bd689a6d300efdf81bc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Thu, 14 May 2020 13:51:46 +0200 Subject: [PATCH 08/41] fix: ensure all intervals/timeouts/observers are cleared when component is destroyed (#5362) * chore: tests cleanup * Only stub components when really needed * Update button-toolbar.spec.js * Update carousel-slide.spec.js * Update form-checkbox-group.spec.js * Update form-radio-group.spec.js * Update tabs.spec.js * Update click-out.spec.js * Update focus-in.spec.js * Update dom.spec.js * fix: ensure all intervals/timeouts/observers are cleared * Unify variable name for non-reactive properties * Shave off some bytes * Update visible.js * Update mixin-tbody.js * Update modal.js * Update carousel.js Co-authored-by: Troy Morehouse --- src/components/carousel/carousel.js | 70 +++++++++++-------- .../form-spinbutton/form-spinbutton.js | 2 + src/components/modal/modal.js | 31 ++++---- .../table/helpers/mixin-filtering.js | 15 ++-- src/components/table/helpers/mixin-tbody.js | 3 + src/components/tabs/tabs.js | 17 ++--- src/components/tooltip/helpers/bv-popper.js | 10 +-- src/components/tooltip/helpers/bv-tooltip.js | 14 ++-- src/components/tooltip/tooltip.js | 32 +++++---- src/directives/scrollspy/scrollspy.class.js | 22 +++--- src/directives/visible/visible.js | 9 +-- src/mixins/dropdown.js | 12 ++-- src/mixins/form-text.js | 7 +- src/utils/transporter.js | 27 ++++--- 14 files changed, 137 insertions(+), 134 deletions(-) diff --git a/src/components/carousel/carousel.js b/src/components/carousel/carousel.js index 775456cc7a6..67a7ca5e534 100644 --- a/src/components/carousel/carousel.js +++ b/src/components/carousel/carousel.js @@ -205,9 +205,10 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ }, created() { // Create private non-reactive props - this._intervalId = null - this._animationTimeout = null - this._touchTimeout = null + this.$_interval = null + this.$_animationTimeout = null + this.$_touchTimeout = null + this.$_observer = null // Set initial paused state this.isPaused = !(toInteger(this.interval, 0) > 0) }, @@ -217,22 +218,39 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ // Get all slides this.updateSlides() // Observe child changes so we can update slide list - observeDom(this.$refs.inner, this.updateSlides.bind(this), { - subtree: false, - childList: true, - attributes: true, - attributeFilter: ['id'] - }) + this.setObserver(true) }, beforeDestroy() { - clearTimeout(this._animationTimeout) - clearTimeout(this._touchTimeout) - clearInterval(this._intervalId) - this._intervalId = null - this._animationTimeout = null - this._touchTimeout = null + this.clearInterval() + this.clearAnimationTimeout() + this.clearTouchTimeout() + this.setObserver(false) }, methods: { + clearInterval() { + clearInterval(this.$_interval) + this.$_interval = null + }, + clearAnimationTimeout() { + clearTimeout(this.$_animationTimeout) + this.$_animationTimeout = null + }, + clearTouchTimeout() { + clearTimeout(this.$_touchTimeout) + this.$_touchTimeout = null + }, + setObserver(on = false) { + this.$_observer && this.$_observer.disconnect() + this.$_observer = null + if (on) { + this.$_observer = observeDom(this.$refs.inner, this.updateSlides.bind(this), { + subtree: false, + childList: true, + attributes: true, + attributeFilter: ['id'] + }) + } + }, // Set slide setSlide(slide, direction = null) { // Don't animate when page is not visible @@ -286,10 +304,7 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ if (!evt) { this.isPaused = true } - if (this._intervalId) { - clearInterval(this._intervalId) - this._intervalId = null - } + this.clearInterval() }, // Start auto rotate slides start(evt) { @@ -297,13 +312,10 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ this.isPaused = false } /* istanbul ignore next: most likely will never happen, but just in case */ - if (this._intervalId) { - clearInterval(this._intervalId) - this._intervalId = null - } + this.clearInterval() // Don't start if no interval, or less than 2 slides if (this.interval && this.numSlides > 1) { - this._intervalId = setInterval(this.next, mathMax(1000, this.interval)) + this.$_interval = setInterval(this.next, mathMax(1000, this.interval)) } }, // Restart auto rotate slides when focus/hover leaves the carousel @@ -362,7 +374,7 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ eventOff(currentSlide, evt, onceTransEnd, EVENT_OPTIONS_NO_CAPTURE) ) } - this._animationTimeout = null + this.clearAnimationTimeout() removeClass(nextSlide, dirClass) removeClass(nextSlide, overlayClass) addClass(nextSlide, 'active') @@ -387,7 +399,7 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ ) } // Fallback to setTimeout() - this._animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION) + this.$_animationTimeout = setTimeout(onceTransEnd, TRANS_DURATION) } if (isCycling) { this.start(false) @@ -480,10 +492,8 @@ export const BCarousel = /*#__PURE__*/ Vue.extend({ // is NOT fired) and after a timeout (to allow for mouse compatibility // events to fire) we explicitly restart cycling this.pause(false) - if (this._touchTimeout) { - clearTimeout(this._touchTimeout) - } - this._touchTimeout = setTimeout( + this.clearTouchTimeout() + this.$_touchTimeout = setTimeout( this.start, TOUCH_EVENT_COMPAT_WAIT + mathMax(1000, this.interval) ) diff --git a/src/components/form-spinbutton/form-spinbutton.js b/src/components/form-spinbutton/form-spinbutton.js index 47ba175c8f0..54ccbd3a209 100644 --- a/src/components/form-spinbutton/form-spinbutton.js +++ b/src/components/form-spinbutton/form-spinbutton.js @@ -466,6 +466,8 @@ export const BFormSpinbutton = /*#__PURE__*/ Vue.extend({ resetTimers() { clearTimeout(this.$_autoDelayTimer) clearInterval(this.$_autoRepeatTimer) + this.$_autoDelayTimer = null + this.$_autoRepeatTimer = null }, clearRepeat() { this.resetTimers() diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index a2363652ec4..36ce83a9e2b 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -450,7 +450,7 @@ export const BModal = /*#__PURE__*/ Vue.extend({ }, created() { // Define non-reactive properties - this._observer = null + this.$_observer = null }, mounted() { // Set initial z-index as queried from the DOM @@ -470,10 +470,7 @@ export const BModal = /*#__PURE__*/ Vue.extend({ }, beforeDestroy() { // Ensure everything is back to normal - if (this._observer) { - this._observer.disconnect() - this._observer = null - } + this.setObserver(false) if (this.isVisible) { this.isVisible = false this.isShow = false @@ -481,6 +478,17 @@ export const BModal = /*#__PURE__*/ Vue.extend({ } }, methods: { + setObserver(on = false) { + this.$_observer && this.$_observer.disconnect() + this.$_observer = null + if (on) { + this.$_observer = observeDom( + this.$refs.content, + this.checkModalOverflow.bind(this), + OBSERVER_CONFIG + ) + } + }, // Private method to update the v-model updateModel(val) { if (val !== this.visible) { @@ -562,10 +570,7 @@ export const BModal = /*#__PURE__*/ Vue.extend({ return } // Stop observing for content changes - if (this._observer) { - this._observer.disconnect() - this._observer = null - } + this.setObserver(false) // Trigger the hide transition this.isVisible = false // Update the v-model @@ -615,13 +620,9 @@ export const BModal = /*#__PURE__*/ Vue.extend({ // Update the v-model this.updateModel(true) this.$nextTick(() => { - // In a nextTick in case modal content is lazy // Observe changes in modal content and adjust if necessary - this._observer = observeDom( - this.$refs.content, - this.checkModalOverflow.bind(this), - OBSERVER_CONFIG - ) + // In a `$nextTick()` in case modal content is lazy + this.setObserver(true) }) }) }, diff --git a/src/components/table/helpers/mixin-filtering.js b/src/components/table/helpers/mixin-filtering.js index 2e1d5b36195..deb4646bec6 100644 --- a/src/components/table/helpers/mixin-filtering.js +++ b/src/components/table/helpers/mixin-filtering.js @@ -101,8 +101,7 @@ export default { // Watch for debounce being set to 0 computedFilterDebounce(newVal) { if (!newVal && this.$_filterTimer) { - clearTimeout(this.$_filterTimer) - this.$_filterTimer = null + this.clearFilterTimer() this.localFilter = this.filterSanitize(this.filter) } }, @@ -113,8 +112,7 @@ export default { deep: true, handler(newCriteria) { const timeout = this.computedFilterDebounce - clearTimeout(this.$_filterTimer) - this.$_filterTimer = null + this.clearFilterTimer() if (timeout && timeout > 0) { // If we have a debounce time, delay the update of `localFilter` this.$_filterTimer = setTimeout(() => { @@ -155,7 +153,7 @@ export default { } }, created() { - // Create non-reactive prop where we store the debounce timer id + // Create private non-reactive props this.$_filterTimer = null // If filter is "pre-set", set the criteria // This will trigger any watchers/dependents @@ -167,10 +165,13 @@ export default { }) }, beforeDestroy() /* istanbul ignore next */ { - clearTimeout(this.$_filterTimer) - this.$_filterTimer = null + this.clearFilterTimer() }, methods: { + clearFilterTimer() { + clearTimeout(this.$_filterTimer) + this.$_filterTimer = null + }, filterSanitize(criteria) { // Sanitizes filter criteria based on internal or external filtering if ( diff --git a/src/components/table/helpers/mixin-tbody.js b/src/components/table/helpers/mixin-tbody.js index 6518aaf9985..2a5d4c38647 100644 --- a/src/components/table/helpers/mixin-tbody.js +++ b/src/components/table/helpers/mixin-tbody.js @@ -17,6 +17,9 @@ const props = { export default { mixins: [tbodyRowMixin], props, + beforeDestroy() { + this.$_bodyFieldSlotNameCache = null + }, methods: { // Helper methods getTbodyTrs() { diff --git a/src/components/tabs/tabs.js b/src/components/tabs/tabs.js index eb53b56c435..75008b57eab 100644 --- a/src/components/tabs/tabs.js +++ b/src/components/tabs/tabs.js @@ -312,8 +312,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({ } }, created() { + // Create private non-reactive props + this.$_observer = null this.currentTab = toInteger(this.value, -1) - this._bvObserver = null // For SSR and to make sure only a single tab is shown on mount // We wrap this in a `$nextTick()` to ensure the child tabs have been created this.$nextTick(() => { @@ -362,11 +363,11 @@ export const BTabs = /*#__PURE__*/ Vue.extend({ unregisterTab(tab) { this.registeredTabs = this.registeredTabs.slice().filter(t => t !== tab) }, + // DOM observer is needed to detect changes in order of tabs setObserver(on) { - // DOM observer is needed to detect changes in order of tabs + this.$_observer && this.$_observer.disconnect() + this.$_observer = null if (on) { - // Make sure no existing observer running - this.setObserver(false) const self = this /* istanbul ignore next: difficult to test mutation observer in JSDOM */ const handler = () => { @@ -379,18 +380,12 @@ export const BTabs = /*#__PURE__*/ Vue.extend({ }) } // Watch for changes to sub components - this._bvObserver = observeDom(this.$refs.tabsContainer, handler, { + this.$_observer = observeDom(this.$refs.tabsContainer, handler, { childList: true, subtree: false, attributes: true, attributeFilter: ['id'] }) - } else { - /* istanbul ignore next */ - if (this._bvObserver && this._bvObserver.disconnect) { - this._bvObserver.disconnect() - } - this._bvObserver = null } }, getTabs() { diff --git a/src/components/tooltip/helpers/bv-popper.js b/src/components/tooltip/helpers/bv-popper.js index a1360184c50..2cace4baa7b 100644 --- a/src/components/tooltip/helpers/bv-popper.js +++ b/src/components/tooltip/helpers/bv-popper.js @@ -157,10 +157,10 @@ export const BVPopper = /*#__PURE__*/ Vue.extend({ updated() { // Update popper if needed // TODO: Should this be a watcher on `this.popperConfig` instead? - this.popperUpdate() + this.updatePopper() }, beforeDestroy() { - this.popperDestroy() + this.destroyPopper() }, destroyed() { // Make sure template is removed from DOM @@ -198,16 +198,16 @@ export const BVPopper = /*#__PURE__*/ Vue.extend({ return this.offset }, popperCreate(el) { - this.popperDestroy() + this.destroyPopper() // We use `el` rather than `this.$el` just in case the original // mountpoint root element type was changed by the template this.$_popper = new Popper(this.target, el, this.popperConfig) }, - popperDestroy() { + destroyPopper() { this.$_popper && this.$_popper.destroy() this.$_popper = null }, - popperUpdate() { + updatePopper() { this.$_popper && this.$_popper.scheduleUpdate() }, popperPlacementChange(data) { diff --git a/src/components/tooltip/helpers/bv-tooltip.js b/src/components/tooltip/helpers/bv-tooltip.js index c7800d74b60..656dbfca221 100644 --- a/src/components/tooltip/helpers/bv-tooltip.js +++ b/src/components/tooltip/helpers/bv-tooltip.js @@ -343,7 +343,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({ this.clearActiveTriggers() this.localPlacementTarget = null try { - this.$_tip && this.$_tip.$destroy() + this.$_tip.$destroy() } catch {} this.$_tip = null this.removeAriaDescribedby() @@ -552,16 +552,12 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({ return this.isDropdown() && target && select(DROPDOWN_OPEN_SELECTOR, target) }, clearHoverTimeout() { - if (this.$_hoverTimeout) { - clearTimeout(this.$_hoverTimeout) - this.$_hoverTimeout = null - } + clearTimeout(this.$_hoverTimeout) + this.$_hoverTimeout = null }, clearVisibilityInterval() { - if (this.$_visibleInterval) { - clearInterval(this.$_visibleInterval) - this.$_visibleInterval = null - } + clearInterval(this.$_visibleInterval) + this.$_visibleInterval = null }, clearActiveTriggers() { for (const trigger in this.activeTrigger) { diff --git a/src/components/tooltip/tooltip.js b/src/components/tooltip/tooltip.js index 512b38625d1..31ccc1c926a 100644 --- a/src/components/tooltip/tooltip.js +++ b/src/components/tooltip/tooltip.js @@ -144,12 +144,12 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ }, watch: { show(show, oldVal) { - if (show !== oldVal && show !== this.localShow && this.$_bv_toolpop) { + if (show !== oldVal && show !== this.localShow && this.$_toolpop) { if (show) { - this.$_bv_toolpop.show() + this.$_toolpop.show() } else { // We use `forceHide()` to override any active triggers - this.$_bv_toolpop.forceHide() + this.$_toolpop.forceHide() } } }, @@ -166,8 +166,8 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ }, templateData() { this.$nextTick(() => { - if (this.$_bv_toolpop) { - this.$_bv_toolpop.updateData(this.templateData) + if (this.$_toolpop) { + this.$_toolpop.updateData(this.templateData) } }) }, @@ -177,8 +177,8 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ } }, created() { - // Non reactive properties - this.$_bv_toolpop = null + // Create private non-reactive props + this.$_toolpop = null }, updated() { // Update the `propData` object @@ -192,8 +192,10 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ this.$off('disable', this.doDisable) this.$off('enable', this.doEnable) // Destroy the tip instance - this.$_bv_toolpop && this.$_bv_toolpop.$destroy() - this.$_bv_toolpop = null + if (this.$_toolpop) { + this.$_toolpop.$destroy() + this.$_toolpop = null + } }, mounted() { // Instantiate a new BVTooltip instance @@ -207,7 +209,7 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ // Pass down the scoped style attribute if available const scopeId = getScopId(this) || getScopId(this.$parent) // Create the instance - const $toolpop = (this.$_bv_toolpop = new Component({ + const $toolpop = (this.$_toolpop = new Component({ parent: this, // Pass down the scoped style ID _scopeId: scopeId || undefined @@ -236,7 +238,7 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ this.$on('enable', this.doEnable) // Initially show tooltip? if (this.localShow) { - this.$_bv_toolpop && this.$_bv_toolpop.show() + $toolpop.show() } }) }, @@ -307,16 +309,16 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({ }, // --- Local event listeners --- doOpen() { - !this.localShow && this.$_bv_toolpop && this.$_bv_toolpop.show() + !this.localShow && this.$_toolpop && this.$_toolpop.show() }, doClose() { - this.localShow && this.$_bv_toolpop && this.$_bv_toolpop.hide() + this.localShow && this.$_toolpop && this.$_toolpop.hide() }, doDisable() { - this.$_bv_toolpop && this.$_bv_toolpop.disable() + this.$_toolpop && this.$_toolpop.disable() }, doEnable() { - this.$_bv_toolpop && this.$_bv_toolpop.enable() + this.$_toolpop && this.$_toolpop.enable() } }, render(h) { diff --git a/src/directives/scrollspy/scrollspy.class.js b/src/directives/scrollspy/scrollspy.class.js index 65c82b382ad..4522789b4c1 100644 --- a/src/directives/scrollspy/scrollspy.class.js +++ b/src/directives/scrollspy/scrollspy.class.js @@ -134,8 +134,8 @@ class ScrollSpy /* istanbul ignore next: not easy to test */ { this.$activeTarget = null this.$scrollHeight = 0 this.$resizeTimeout = null - this.$obs_scroller = null - this.$obs_targets = null + this.$scrollerObserver = null + this.$targetsObserver = null this.$root = $root || null this.$config = null @@ -223,16 +223,12 @@ class ScrollSpy /* istanbul ignore next: not easy to test */ { setObservers(on) { // We observe both the scroller for content changes, and the target links - if (this.$obs_scroller) { - this.$obs_scroller.disconnect() - this.$obs_scroller = null - } - if (this.$obs_targets) { - this.$obs_targets.disconnect() - this.$obs_targets = null - } + this.$scrollerObserver && this.$scrollerObserver.disconnect() + this.$targetsObserver && this.$targetsObserver.disconnect() + this.$scrollerObserver = null + this.$targetsObserver = null if (on) { - this.$obs_targets = observeDom( + this.$targetsObserver = observeDom( this.$el, () => { this.handleEvent('mutation') @@ -244,7 +240,7 @@ class ScrollSpy /* istanbul ignore next: not easy to test */ { attributeFilter: ['href'] } ) - this.$obs_scroller = observeDom( + this.$scrollerObserver = observeDom( this.getScroller(), () => { this.handleEvent('mutation') @@ -276,7 +272,7 @@ class ScrollSpy /* istanbul ignore next: not easy to test */ { } if (type === 'scroll') { - if (!this.$obs_scroller) { + if (!this.$scrollerObserver) { // Just in case we are added to the DOM before the scroll target is // We re-instantiate our listeners, just in case this.listen() diff --git a/src/directives/visible/visible.js b/src/directives/visible/visible.js index ec7889f945e..f17d1cc0465 100644 --- a/src/directives/visible/visible.js +++ b/src/directives/visible/visible.js @@ -38,7 +38,7 @@ import { clone, keys } from '../../utils/object' const OBSERVER_PROP_NAME = '__bv__visibility_observer' -const onlyDgitsRE = /^\d+$/ +const RX_ONLY_DIGITS = /^\d+$/ class VisibilityObserver { constructor(el, options, vnode) { @@ -114,11 +114,8 @@ class VisibilityObserver { } stop() { - const observer = this.observer /* istanbul ignore next */ - if (observer && observer.disconnect) { - observer.disconnect() - } + this.observer && this.observer.disconnect() this.observer = null } } @@ -141,7 +138,7 @@ const bind = (el, { value, modifiers }, vnode) => { // Parse modifiers keys(modifiers).forEach(mod => { /* istanbul ignore else: Until is switched to use this directive */ - if (onlyDgitsRE.test(mod)) { + if (RX_ONLY_DIGITS.test(mod)) { options.margin = `${mod}px` } else if (mod.toLowerCase() === 'once') { options.once = true diff --git a/src/mixins/dropdown.js b/src/mixins/dropdown.js index cc92f840b6c..2068babd9ca 100644 --- a/src/mixins/dropdown.js +++ b/src/mixins/dropdown.js @@ -169,7 +169,7 @@ export default { } }, created() { - // Create non-reactive property + // Create private non-reactive props this.$_popper = null }, /* istanbul ignore next */ @@ -236,16 +236,14 @@ export default { this.destroyPopper() this.$_popper = new Popper(element, this.$refs.menu, this.getPopperConfig()) }, + // Ensure popper event listeners are removed cleanly destroyPopper() { - // Ensure popper event listeners are removed cleanly - if (this.$_popper) { - this.$_popper.destroy() - } + this.$_popper && this.$_popper.destroy() this.$_popper = null }, + // Instructs popper to re-computes the dropdown position + // useful if the content changes size updatePopper() /* istanbul ignore next: not easy to test */ { - // Instructs popper to re-computes the dropdown position - // useful if the content changes size try { this.$_popper.scheduleUpdate() } catch {} diff --git a/src/mixins/form-text.js b/src/mixins/form-text.js index cc30ea4cdac..321dd5ef73b 100644 --- a/src/mixins/form-text.js +++ b/src/mixins/form-text.js @@ -117,9 +117,12 @@ export default { } } }, - mounted() { - // Create non-reactive property and set up destroy handler + created() { + // Create private non-reactive props this.$_inputDebounceTimer = null + }, + mounted() { + // Set up destroy handler this.$on('hook:beforeDestroy', this.clearDebounce) // Preset the internal state const value = this.value diff --git a/src/utils/transporter.js b/src/utils/transporter.js index cfabaed40cd..c0ea07d15b9 100644 --- a/src/utils/transporter.js +++ b/src/utils/transporter.js @@ -88,8 +88,9 @@ export const BTransporterSingle = /*#__PURE__*/ Vue.extend({ } }, created() { - this._bv_defaultFn = null - this._bv_target = null + // Create private non-reactive props + this.$_defaultFn = null + this.$_target = null }, beforeMount() { this.mountTarget() @@ -105,7 +106,7 @@ export const BTransporterSingle = /*#__PURE__*/ Vue.extend({ }, beforeDestroy() { this.unmountTarget() - this._bv_defaultFn = null + this.$_defaultFn = null }, methods: { // Get the element which the target should be appended to @@ -120,12 +121,12 @@ export const BTransporterSingle = /*#__PURE__*/ Vue.extend({ }, // Mount the target mountTarget() { - if (!this._bv_target) { + if (!this.$_target) { const container = this.getContainer() if (container) { const el = document.createElement('div') container.appendChild(el) - this._bv_target = new BTransporterTargetSingle({ + this.$_target = new BTransporterTargetSingle({ el, parent: this, propsData: { @@ -138,30 +139,28 @@ export const BTransporterSingle = /*#__PURE__*/ Vue.extend({ }, // Update the content of the target updateTarget() { - if (isBrowser && this._bv_target) { + if (isBrowser && this.$_target) { const defaultFn = this.$scopedSlots.default if (!this.disabled) { /* istanbul ignore else: only applicable in Vue 2.5.x */ - if (defaultFn && this._bv_defaultFn !== defaultFn) { + if (defaultFn && this.$_defaultFn !== defaultFn) { // We only update the target component if the scoped slot // function is a fresh one. The new slot syntax (since Vue 2.6) // can cache unchanged slot functions and we want to respect that here - this._bv_target.updatedNodes = defaultFn + this.$_target.updatedNodes = defaultFn } else if (!defaultFn) { // We also need to be back compatible with non-scoped default slot (i.e. 2.5.x) - this._bv_target.updatedNodes = this.$slots.default + this.$_target.updatedNodes = this.$slots.default } } // Update the scoped slot function cache - this._bv_defaultFn = defaultFn + this.$_defaultFn = defaultFn } }, // Unmount the target unmountTarget() { - if (this._bv_target) { - this._bv_target.$destroy() - this._bv_target = null - } + this.$_target && this.$_target.$destroy() + this.$_target = null } }, render(h) { From 8f3ca30e4d51b5e97f9c4f301c31254a8b060980 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 14 May 2020 17:43:07 -0300 Subject: [PATCH 09/41] feat(types): create declarations for `` and `` context event objects (closes #5366) (#5374) * feat(types): create declarations for `` and `` context event objects * Update index.d.ts * Update index.d.ts * Update index.d.ts * Update index.d.ts --- src/components/calendar/index.d.ts | 16 ++++++++++++++++ src/components/time/index.d.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/components/calendar/index.d.ts b/src/components/calendar/index.d.ts index ac3fd94e599..52e97f37c60 100644 --- a/src/components/calendar/index.d.ts +++ b/src/components/calendar/index.d.ts @@ -10,3 +10,19 @@ export declare class BCalendar extends BvComponent { focus: () => void blur: () => void } + +// --- Interfaces --- + +// Calendar context event object +export interface BcCalendarCtxObject { + readonly selectedFormatted: string + readonly selectedYMD: string + readonly selectedDate: Date | null + readonly activeFormatted: string + readonly activeYMD: string + readonly activeDate: Date | null + readonly disabled: boolean + readonly locale: string + readonly calendarLocale: string + readonly rtl: boolean +} diff --git a/src/components/time/index.d.ts b/src/components/time/index.d.ts index 828167ddc15..0de6382bf87 100644 --- a/src/components/time/index.d.ts +++ b/src/components/time/index.d.ts @@ -10,3 +10,18 @@ export declare class BTime extends BvComponent { focus: () => void blur: () => void } + +// --- Interfaces --- + +// Time context event object +export interface BvTimeCtxEvent { + readonly formatted: string + readonly value: string + readonly hours: number | null + readonly minutes: number | null + readonly seconds: number | null + readonly hourCycle: string + readonly hour12: boolean + readonly locale: string + readonly isRtl: boolean +} From 6d29e1cff6c4fd42b3f60f86bd017d8601de3956 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Thu, 14 May 2020 18:16:01 -0300 Subject: [PATCH 10/41] feat(b-link): add support 3rd party router links such as Gridsome's `` (closes #2627) (#5358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(b-link): add support 3rd party router links such as Gridsome's `` * Update link.js * Update router.js * Update link.js * Update router.js * Update link.js * Update link.spec.js * Update link.spec.js * Update link.spec.js * Update link.spec.js * Update router.js * Update router.js * Update link.spec.js * Update link.spec.js * Update common-props.json * Update link.js * Update link.js * Update common-props.json * Update README.md * Update README.md * Update avatar.js * Update common-props.json * Update README.md * Update README.md * Update README.md * Update common-props.json * Update package.json * Update common-props.json * Update package.json * Update README.md * Update avatar.js * Update README.md * Merge remote-tracking branch 'origin/dev' into blink-gridsome * Make sure to always omit ``'s `event` prop for other components * Add `routerComponentName` to global config * Update pagination-nav.js * Update pagination-nav.js * Omit `routerTag` for all other components * Unify link detection in other components * Update common-props.json Co-authored-by: Jacob Müller --- docs/common-props.json | 35 ++++++++------- .../markdown/reference/router-links/README.md | 32 +++++++++++++- src/components/avatar/avatar.js | 32 ++++---------- src/components/badge/badge.js | 16 ++++--- src/components/breadcrumb/breadcrumb-link.js | 5 ++- src/components/button/button.js | 17 +++---- src/components/dropdown/dropdown-item.js | 4 +- src/components/dropdown/package.json | 2 +- src/components/link/README.md | 11 +++++ src/components/link/link.js | 20 +++++++-- src/components/link/link.spec.js | 27 +++++++++++- src/components/link/package.json | 15 ++++++- src/components/list-group/list-group-item.js | 44 +++++++++++-------- src/components/nav/nav-item.js | 4 +- src/components/navbar/navbar-brand.js | 11 +++-- src/components/navbar/navbar.js | 8 +++- .../pagination-nav/pagination-nav.js | 43 ++++++------------ src/components/table/td.js | 6 ++- src/components/toast/toast.js | 28 ++++++------ src/utils/config-defaults.js | 3 ++ src/utils/dom.js | 4 ++ src/utils/router.js | 26 ++++++++--- 22 files changed, 251 insertions(+), 142 deletions(-) diff --git a/docs/common-props.json b/docs/common-props.json index 78ccfa2bf83..25cb814ad55 100644 --- a/docs/common-props.json +++ b/docs/common-props.json @@ -197,44 +197,49 @@ "active": { "description": "When set to 'true', places the component in the active state with active styling" }, + "href": { + "description": " prop: Denotes the target URL of the link for standard a links" + }, "rel": { - "description": "Sets the 'rel' attribute on the rendered link" + "description": " prop: Sets the 'rel' attribute on the rendered link" }, "target": { - "description": "Sets the 'target' attribute on the rendered link" - }, - "href": { - "description": "Denotes the target URL of the link for standard a links" + "description": " prop: Sets the 'target' attribute on the rendered link" }, "to": { - "description": "router-link prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object" + "description": " prop: Denotes the target route of the link. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object" }, "replace": { - "description": "router-link prop: Setting the replace prop will call 'router.replace()' instead of 'router.push()' when clicked, so the navigation will not leave a history record" + "description": " prop: Setting the replace prop will call 'router.replace()' instead of 'router.push()' when clicked, so the navigation will not leave a history record" }, "append": { - "description": "router-link prop: Setting append prop always appends the relative path to the current path" + "description": " prop: Setting append prop always appends the relative path to the current path" }, "exact": { - "description": "router-link prop: The default active class matching behavior is inclusive match. Setting this prop forces the mode to exactly match the route" + "description": " prop: The default active class matching behavior is inclusive match. Setting this prop forces the mode to exactly match the route" }, "activeClass": { - "description": "router-link prop: Configure the active CSS class applied when the link is active. Typically you will want to set this to class name 'active'" + "description": " prop: Configure the active CSS class applied when the link is active. Typically you will want to set this to class name 'active'" }, "exactActiveClass": { - "description": "router-link prop: Configure the active CSS class applied when the link is active with exact match. Typically you will want to set this to class name 'active'" + "description": " prop: Configure the active CSS class applied when the link is active with exact match. Typically you will want to set this to class name 'active'" }, "routerTag": { - "description": "router-link prop: Specify which tag to render, and it will still listen to click events for navigation. 'router-tag' translates to the tag prop on the final rendered router-link. Typically you should use the default value" + "description": " prop: Specify which tag to render, and it will still listen to click events for navigation. 'router-tag' translates to the tag prop on the final rendered router-link. Typically you should use the default value" }, "event": { - "description": "router-link prop: Specify the event that triggers the link. In most cases you should leave this as the default" + "description": " prop: Specify the event that triggers the link. In most cases you should leave this as the default" }, "prefetch": { - "description": "nuxt-link prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'prefetch' to 'true' or 'false' will overwrite the default value of 'router.prefetchLinks'", + "description": " prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'prefetch' to 'true' or 'false' will overwrite the default value of 'router.prefetchLinks'", "version": "2.15.0" }, "noPrefetch": { - "description": "nuxt-link prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'no-prefetch' will disabled this feature for the specific link" + "description": " prop: To improve the responsiveness of your Nuxt.js applications, when the link will be displayed within the viewport, Nuxt.js will automatically prefetch the code splitted page. Setting 'no-prefetch' will disabled this feature for the specific link" + }, + "routerComponentName": { + "description": " prop: BootstrapVue auto detects between `` and ``. In cases where you want to use a 3rd party link component based on ``, set this prop to the component name. e.g. set it to 'g-link' if you are using Gridsome (note only `` specific props are passed to the component)", + "version": "2.15.0", + "settings": true } } diff --git a/docs/markdown/reference/router-links/README.md b/docs/markdown/reference/router-links/README.md index 796cd12ee93..88e7a91de36 100644 --- a/docs/markdown/reference/router-links/README.md +++ b/docs/markdown/reference/router-links/README.md @@ -10,7 +10,8 @@ In the following sections, we are using the `` component to render router links. `` is the building block of most of BootstrapVue's _actionable_ components. You could use any other component that supports link generation such as [``](/docs/components/link), -[``](/docs/components/button), [``](/docs/components/breadcrumb), +[``](/docs/components/button), [``](/docs/components/avatar), +[``](/docs/components/breadcrumb), [``](/docs/components/list-group), [``](/docs/components/nav), [``](/docs/components/dropdown), and [``](/docs/components/pagination-nav). Note that not all props are available on @@ -203,3 +204,32 @@ disabled this feature for the specific link. **Note:** If you have prefetching disabled in your `nuxt.config.js` configuration (`router: { prefetchLinks: false }`), or are using a version of Nuxt.js `< 2.4.0`, then this prop will have no effect. + +## Third-party router link support + +v2.15.0+ + +BootstrapVue auto detects using `` and `` link components. Some 3rd party +frameworks also provide customized versions of ``, such as +[Gridsome's `` component](https://gridsome.org/docs/linking/). BootstrapVue can support +these third party `` compatible components via the use of the `router-component-name` +prop. All `vue-router` props (excluding `` specific props) will be passed to the +specified router link component. + +**Notes:** + +- The 3rd party component will only be used when the `to` prop is set. +- Not all 3rd party components support all props supported by ``, nor do not support + fully qualified domain name URLs, nor hash only URLs. Refer to the 3rd party component + documentation for details. + +### `router-component-name` + +- type: `string` +- default: `undefined` +- availability: BootstrapVue 2.15.0+ + +Set this prop to the name of the `` compatible component, e.g. `'g-link'` for +[Gridsome](https://gridsome.org/). + +If left at the default, BootstrapVue will automatically select `` or ``. diff --git a/src/components/avatar/avatar.js b/src/components/avatar/avatar.js index 6501aede88e..d9974a53b6a 100644 --- a/src/components/avatar/avatar.js +++ b/src/components/avatar/avatar.js @@ -3,6 +3,8 @@ import pluckProps from '../../utils/pluck-props' import { getComponentConfig } from '../../utils/config' import { isNumber, isString, isUndefinedOrNull } from '../../utils/inspect' import { toFloat } from '../../utils/number' +import { omit } from '../../utils/object' +import { isLink } from '../../utils/router' import { BButton } from '../button/button' import { BLink, props as BLinkProps } from '../link/link' import { BIcon } from '../../icons/icon' @@ -25,23 +27,7 @@ const DEFAULT_SIZES = { } // --- Props --- -const linkProps = pluckProps( - [ - 'href', - 'rel', - 'target', - 'disabled', - 'to', - 'append', - 'replace', - 'activeClass', - 'exact', - 'exactActiveClass', - 'prefetch', - 'noPrefetch' - ], - BLinkProps -) +const linkProps = omit(BLinkProps, ['active', 'event', 'routerTag']) const props = { src: { @@ -208,14 +194,14 @@ export const BAvatar = /*#__PURE__*/ Vue.extend({ fontStyle, marginStyle, computedSize: size, - button: isButton, + button, buttonType: type, badge, badgeVariant, badgeStyle } = this - const isBLink = !isButton && (this.href || this.to) - const tag = isButton ? BButton : isBLink ? BLink : 'span' + const link = !button && isLink(this) + const tag = button ? BButton : link ? BLink : 'span' const alt = this.alt || null const ariaLabel = this.ariaLabel || null @@ -261,7 +247,7 @@ export const BAvatar = /*#__PURE__*/ Vue.extend({ staticClass: CLASS_NAME, class: { // We use badge styles for theme variants when not rendering `BButton` - [`badge-${variant}`]: !isButton && variant, + [`badge-${variant}`]: !button && variant, // Rounding/Square rounded: rounded === true, [`rounded-${rounded}`]: rounded && rounded !== true, @@ -270,8 +256,8 @@ export const BAvatar = /*#__PURE__*/ Vue.extend({ }, style: { width: size, height: size, ...marginStyle }, attrs: { 'aria-label': ariaLabel || null }, - props: isButton ? { variant, disabled, type } : isBLink ? pluckProps(linkProps, this) : {}, - on: isBLink || isButton ? { click: this.onClick } : {} + props: button ? { variant, disabled, type } : link ? pluckProps(linkProps, this) : {}, + on: button || link ? { click: this.onClick } : {} } return h(tag, componentData, [$content, $badge]) diff --git a/src/components/badge/badge.js b/src/components/badge/badge.js index ab2399e16fe..47941d8e2ba 100644 --- a/src/components/badge/badge.js +++ b/src/components/badge/badge.js @@ -2,12 +2,17 @@ import Vue from '../../utils/vue' import pluckProps from '../../utils/pluck-props' import { mergeData } from 'vue-functional-data-merge' import { getComponentConfig } from '../../utils/config' -import { clone } from '../../utils/object' +import { omit } from '../../utils/object' +import { isLink } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' +// --- Constants --- + const NAME = 'BBadge' -const linkProps = clone(BLinkProps) +// --- Props --- + +const linkProps = omit(BLinkProps, ['event', 'routerTag']) delete linkProps.href.default delete linkProps.to.default @@ -27,14 +32,15 @@ export const props = { ...linkProps } +// --- Main component --- // @vue/component export const BBadge = /*#__PURE__*/ Vue.extend({ name: NAME, functional: true, props, render(h, { props, data, children }) { - const isBLink = props.href || props.to - const tag = isBLink ? BLink : props.tag + const link = isLink(props) + const tag = link ? BLink : props.tag const componentData = { staticClass: 'badge', @@ -46,7 +52,7 @@ export const BBadge = /*#__PURE__*/ Vue.extend({ disabled: props.disabled } ], - props: isBLink ? pluckProps(linkProps, props) : {} + props: link ? pluckProps(linkProps, props) : {} } return h(tag, mergeData(data, componentData), children) diff --git a/src/components/breadcrumb/breadcrumb-link.js b/src/components/breadcrumb/breadcrumb-link.js index a2cc8b1961e..232d1f99d85 100644 --- a/src/components/breadcrumb/breadcrumb-link.js +++ b/src/components/breadcrumb/breadcrumb-link.js @@ -1,7 +1,8 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' +import Vue from '../../utils/vue' import pluckProps from '../../utils/pluck-props' import { htmlOrText } from '../../utils/html' +import { omit } from '../../utils/object' import { BLink, props as BLinkProps } from '../link/link' export const props = { @@ -17,7 +18,7 @@ export const props = { type: String, default: 'location' }, - ...BLinkProps + ...omit(BLinkProps, ['event', 'routerTag']) } // @vue/component diff --git a/src/components/button/button.js b/src/components/button/button.js index e7c9382c556..7704e66bfbf 100644 --- a/src/components/button/button.js +++ b/src/components/button/button.js @@ -4,10 +4,10 @@ import KeyCodes from '../../utils/key-codes' import pluckProps from '../../utils/pluck-props' import { concat } from '../../utils/array' import { getComponentConfig } from '../../utils/config' -import { addClass, removeClass } from '../../utils/dom' +import { addClass, isTag, removeClass } from '../../utils/dom' import { isBoolean, isEvent, isFunction } from '../../utils/inspect' -import { clone } from '../../utils/object' -import { toString } from '../../utils/string' +import { omit } from '../../utils/object' +import { isLink as isLinkStrict } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' // --- Constants --- @@ -16,7 +16,7 @@ const NAME = 'BButton' // --- Props --- -const linkProps = clone(BLinkProps) +const linkProps = omit(BLinkProps, ['event', 'routerTag']) delete linkProps.href.default delete linkProps.to.default @@ -65,9 +65,6 @@ export const props = { ...btnProps, ...linkProps } // --- Helper methods --- -// Returns `true` if a tag's name equals `name` -const tagIs = (tag, name) => toString(tag).toLowerCase() === toString(name).toLowerCase() - // Focus handler for toggle buttons // Needs class of 'focus' when focused const handleFocus = evt => { @@ -80,13 +77,13 @@ const handleFocus = evt => { // Is the requested button a link? // If tag prop is set to `a`, we use a to get proper disabled handling -const isLink = props => props.href || props.to || tagIs(props.tag, 'a') +const isLink = props => isLinkStrict(props) || isTag(props.tag, 'a') // Is the button to be a toggle button? const isToggle = props => isBoolean(props.pressed) // Is the button "really" a button? -const isButton = props => !(isLink(props) || (props.tag && !tagIs(props.tag, 'button'))) +const isButton = props => !(isLink(props) || (props.tag && !isTag(props.tag, 'button'))) // Is the requested tag not a button or link? const isNonStandardTag = props => !isLink(props) && !isButton(props) @@ -105,7 +102,7 @@ const computeClass = props => [ ] // Compute the link props to pass to b-link (if required) -const computeLinkProps = props => (isLink(props) ? pluckProps(linkProps, props) : null) +const computeLinkProps = props => (isLink(props) ? pluckProps(linkProps, props) : {}) // Compute the attributes for a button const computeAttrs = (props, data) => { diff --git a/src/components/dropdown/dropdown-item.js b/src/components/dropdown/dropdown-item.js index 01af4fe1478..d4c2abacd07 100644 --- a/src/components/dropdown/dropdown-item.js +++ b/src/components/dropdown/dropdown-item.js @@ -1,11 +1,11 @@ import Vue from '../../utils/vue' import { requestAF } from '../../utils/dom' -import { clone } from '../../utils/object' +import { omit } from '../../utils/object' import attrsMixin from '../../mixins/attrs' import normalizeSlotMixin from '../../mixins/normalize-slot' import { BLink, props as BLinkProps } from '../link/link' -export const props = clone(BLinkProps) +export const props = omit(BLinkProps, ['event', 'routerTag']) // @vue/component export const BDropdownItem = /*#__PURE__*/ Vue.extend({ diff --git a/src/components/dropdown/package.json b/src/components/dropdown/package.json index a049dd7a235..4c541db9974 100644 --- a/src/components/dropdown/package.json +++ b/src/components/dropdown/package.json @@ -97,7 +97,7 @@ }, { "prop": "splitTo", - "description": "router-link prop: Denotes the target route of the split button. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object" + "description": " prop: Denotes the target route of the split button. When clicked, the value of the to prop will be passed to router.push() internally, so the value can be either a string or a Location descriptor object" }, { "prop": "splitVariant", diff --git a/src/components/link/README.md b/src/components/link/README.md index 2a381eabbd8..d51b06f6f71 100644 --- a/src/components/link/README.md +++ b/src/components/link/README.md @@ -26,6 +26,17 @@ If your app is running under [Nuxt.js](https://nuxtjs.org), the ``. The `` component supports all the same features as `` (as it is a wrapper component for ``) and more. +### Third party rounter links + +BootstrapVue auto detects using `` and `` link components. Some 3rd party +frameworks also provide customized versions of ``, such as +[Gridsome's `` component](https://gridsome.org/docs/linking/). `` can support these +third party `` compatible components via the use of the `router-component-name` prop. +All `vue-router` props (excluding `` specific props) will be passed to the specified +router link component. + +Note that the 3rd party component will only be used when the `to` prop is set. + ## Links with `href="#"` Typically `` will cause the document to scroll to the top of page when clicked. diff --git a/src/components/link/link.js b/src/components/link/link.js index 539af3514c6..60e5c58036a 100644 --- a/src/components/link/link.js +++ b/src/components/link/link.js @@ -1,6 +1,7 @@ import Vue from '../../utils/vue' import pluckProps from '../../utils/pluck-props' import { concat } from '../../utils/array' +import { getComponentConfig } from '../../utils/config' import { attemptBlur, attemptFocus } from '../../utils/dom' import { isBoolean, isEvent, isFunction, isUndefined } from '../../utils/inspect' import { computeHref, computeRel, computeTag, isRouterLink } from '../../utils/router' @@ -8,6 +9,10 @@ import attrsMixin from '../../mixins/attrs' import listenersMixin from '../../mixins/listeners' import normalizeSlotMixin from '../../mixins/normalize-slot' +// --- Constants --- + +const NAME = 'BLink' + // --- Props --- // specific props @@ -87,7 +92,15 @@ export const props = { default: false }, ...routerLinkProps, - ...nuxtLinkProps + ...nuxtLinkProps, + // To support 3rd party router links based on `` (i.e. `g-link` for Gridsome) + // Default is to auto choose between `` and `` + // Gridsome doesn't provide a mechanism to auto detect and has caveats + // such as not supporting FQDN URLs or hash only URLs + routerComponentName: { + type: String, + default: () => getComponentConfig(NAME, 'routerComponentName') + } } // --- Main component --- @@ -101,7 +114,8 @@ export const BLink = /*#__PURE__*/ Vue.extend({ computed: { computedTag() { // We don't pass `this` as the first arg as we need reactivity of the props - return computeTag({ to: this.to, disabled: this.disabled }, this) + const { to, disabled, routerComponentName } = this + return computeTag({ to, disabled, routerComponentName }, this) }, isRouterLink() { return isRouterLink(this.computedTag) @@ -118,7 +132,7 @@ export const BLink = /*#__PURE__*/ Vue.extend({ const prefetch = this.prefetch return this.isRouterLink ? { - ...pluckProps({ ...routerLinkProps, ...nuxtLinkProps }, this.$props), + ...pluckProps({ ...routerLinkProps, ...nuxtLinkProps }, this), // Coerce `prefetch` value `null` to be `undefined` prefetch: isBoolean(prefetch) ? prefetch : undefined, // Pass `router-tag` as `tag` prop diff --git a/src/components/link/link.spec.js b/src/components/link/link.spec.js index 7f3913abb5e..6f404cfc7b8 100644 --- a/src/components/link/link.spec.js +++ b/src/components/link/link.spec.js @@ -326,6 +326,24 @@ describe('b-link', () => { ] }) + // Fake Gridsome `` component + const GLink = { + name: 'GLink', + props: { + to: { + type: [String, Object], + default: '' + } + }, + render(h) { + // We just us a simple A tag to render the + // fake `` and assume `to` is a string + return h('a', { attrs: { href: this.to } }, [this.$slots.default]) + } + } + + localVue.component('GLink', GLink) + const App = { router, components: { BLink }, @@ -339,6 +357,8 @@ describe('b-link', () => { h('b-link', { props: { to: { path: '/b' } } }, ['to-path-b']), // regular link h('b-link', { props: { href: '/b' } }, ['href-a']), + // g-link + h('b-link', { props: { routerComponentName: 'g-link', to: '/a' } }, ['g-link-a']), h('router-view') ]) } @@ -352,7 +372,7 @@ describe('b-link', () => { expect(wrapper.vm).toBeDefined() expect(wrapper.element.tagName).toBe('MAIN') - expect(wrapper.findAll('a').length).toBe(4) + expect(wrapper.findAll('a').length).toBe(5) const $links = wrapper.findAll('a') @@ -374,6 +394,11 @@ describe('b-link', () => { expect($links.at(3).vm.$options.name).toBe('BLink') expect($links.at(3).vm.$children.length).toBe(0) + expect($links.at(4).vm).toBeDefined() + expect($links.at(4).vm.$options.name).toBe('BLink') + expect($links.at(4).vm.$children.length).toBe(1) + expect($links.at(4).vm.$children[0].$options.name).toBe('GLink') + wrapper.destroy() }) }) diff --git a/src/components/link/package.json b/src/components/link/package.json index 4671251ef80..b5400530c42 100644 --- a/src/components/link/package.json +++ b/src/components/link/package.json @@ -7,7 +7,20 @@ "components": [ { "component": "BLink", - "props": [], + "props": [ + { + "prop": "href", + "description": "Denotes the target URL of the link for standard a links" + }, + { + "prop": "rel", + "description": "Sets the 'rel' attribute on the rendered link" + }, + { + "prop": "target", + "description": "Sets the 'target' attribute on the rendered link" + } + ], "events": [ { "event": "click", diff --git a/src/components/list-group/list-group-item.js b/src/components/list-group/list-group-item.js index f41bf0c3e77..dcfdb6fcae5 100644 --- a/src/components/list-group/list-group-item.js +++ b/src/components/list-group/list-group-item.js @@ -3,7 +3,9 @@ import Vue from '../../utils/vue' import pluckProps from '../../utils/pluck-props' import { arrayIncludes } from '../../utils/array' import { getComponentConfig } from '../../utils/config' -import { clone } from '../../utils/object' +import { isTag } from '../../utils/dom' +import { omit } from '../../utils/object' +import { isLink } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' // --- Constants --- @@ -14,7 +16,7 @@ const actionTags = ['a', 'router-link', 'button', 'b-link'] // --- Props --- -const linkProps = clone(BLinkProps) +const linkProps = omit(BLinkProps, ['event', 'routerTag']) delete linkProps.href.default delete linkProps.to.default @@ -45,13 +47,14 @@ export const BListGroupItem = /*#__PURE__*/ Vue.extend({ functional: true, props, render(h, { props, data, children }) { - const tag = props.button ? 'button' : !props.href && !props.to ? props.tag : BLink - const isAction = Boolean( - props.href || props.to || props.action || props.button || arrayIncludes(actionTags, props.tag) - ) + const { button, variant, active, disabled } = props + const link = isLink(props) + const tag = button ? 'button' : !link ? props.tag : BLink + const action = !!(props.action || link || button || arrayIncludes(actionTags, props.tag)) + const attrs = {} let itemProps = {} - if (tag === 'button') { + if (isTag(tag, 'button')) { if (!data.attrs || !data.attrs.type) { // Add a type for button is one not provided in passed attributes attrs.type = 'button' @@ -63,18 +66,21 @@ export const BListGroupItem = /*#__PURE__*/ Vue.extend({ } else { itemProps = pluckProps(linkProps, props) } - const componentData = { - attrs, - props: itemProps, - staticClass: 'list-group-item', - class: { - [`list-group-item-${props.variant}`]: props.variant, - 'list-group-item-action': isAction, - active: props.active, - disabled: props.disabled - } - } - return h(tag, mergeData(data, componentData), children) + return h( + tag, + mergeData(data, { + attrs, + props: itemProps, + staticClass: 'list-group-item', + class: { + [`list-group-item-${variant}`]: variant, + 'list-group-item-action': action, + active, + disabled + } + }), + children + ) } }) diff --git a/src/components/nav/nav-item.js b/src/components/nav/nav-item.js index a5796140354..28af3ab36da 100644 --- a/src/components/nav/nav-item.js +++ b/src/components/nav/nav-item.js @@ -1,11 +1,11 @@ import { mergeData } from 'vue-functional-data-merge' import Vue from '../../utils/vue' -import { clone } from '../../utils/object' +import { omit } from '../../utils/object' import { BLink, props as BLinkProps } from '../link/link' // --- Props --- -export const props = clone(BLinkProps) +export const props = omit(BLinkProps, ['event', 'routerTag']) // --- Main component --- // @vue/component diff --git a/src/components/navbar/navbar-brand.js b/src/components/navbar/navbar-brand.js index 144f8e2e0ef..835b8801192 100644 --- a/src/components/navbar/navbar-brand.js +++ b/src/components/navbar/navbar-brand.js @@ -1,21 +1,24 @@ import { mergeData } from 'vue-functional-data-merge' import Vue from '../../utils/vue' import pluckProps from '../../utils/pluck-props' -import { clone } from '../../utils/object' +import { omit } from '../../utils/object' import { BLink, props as BLinkProps } from '../link/link' -const linkProps = clone(BLinkProps) +// --- Props --- + +const linkProps = omit(BLinkProps, ['event', 'routerTag']) linkProps.href.default = undefined linkProps.to.default = undefined export const props = { - ...linkProps, tag: { type: String, default: 'div' - } + }, + ...linkProps } +// --- Main component --- // @vue/component export const BNavbarBrand = /*#__PURE__*/ Vue.extend({ name: 'BNavbarBrand', diff --git a/src/components/navbar/navbar.js b/src/components/navbar/navbar.js index eed8ad22cee..5700e79c064 100644 --- a/src/components/navbar/navbar.js +++ b/src/components/navbar/navbar.js @@ -1,10 +1,15 @@ import Vue from '../../utils/vue' import { getComponentConfig, getBreakpoints } from '../../utils/config' +import { isTag } from '../../utils/dom' import { isString } from '../../utils/inspect' import normalizeSlotMixin from '../../mixins/normalize-slot' +// --- Constants --- + const NAME = 'BNavbar' +// --- Props --- + export const props = { tag: { type: String, @@ -35,6 +40,7 @@ export const props = { } } +// --- Main component --- // @vue/component export const BNavbar = /*#__PURE__*/ Vue.extend({ name: NAME, @@ -73,7 +79,7 @@ export const BNavbar = /*#__PURE__*/ Vue.extend({ this.breakpointClass ], attrs: { - role: this.tag === 'nav' ? null : 'navigation' + role: isTag(this.tag, 'nav') ? null : 'navigation' } }, [this.normalizeSlot('default')] diff --git a/src/components/pagination-nav/pagination-nav.js b/src/components/pagination-nav/pagination-nav.js index b3b2678f8b9..fbcccf45c87 100644 --- a/src/components/pagination-nav/pagination-nav.js +++ b/src/components/pagination-nav/pagination-nav.js @@ -7,16 +7,20 @@ import { isBrowser } from '../../utils/env' import { isArray, isUndefined, isFunction, isObject } from '../../utils/inspect' import { mathMax } from '../../utils/math' import { toInteger } from '../../utils/number' +import { omit } from '../../utils/object' import { computeHref, parseQuery } from '../../utils/router' import { toString } from '../../utils/string' import { warn } from '../../utils/warn' import paginationMixin from '../../mixins/pagination' import { props as BLinkProps } from '../link/link' +// --- Constants --- + const NAME = 'BPaginationNav' -// Sanitize the provided number of pages (converting to a number) -export const sanitizeNumberOfPages = value => mathMax(toInteger(value, 0), 1) +// --- Props --- + +const linkProps = omit(BLinkProps, ['event', 'routerTag']) const props = { size: { @@ -61,9 +65,15 @@ const props = { type: Boolean, default: false }, - ...pluckProps(['activeClass', 'exact', 'exactActiveClass', 'prefetch', 'noPrefetch'], BLinkProps) + ...linkProps } +// --- Utility methods --- + +// Sanitize the provided number of pages (converting to a number) +export const sanitizeNumberOfPages = value => mathMax(toInteger(value, 0), 1) + +// --- Main component --- // The render function is brought in via the pagination mixin // @vue/component export const BPaginationNav = /*#__PURE__*/ Vue.extend({ @@ -175,38 +185,13 @@ export const BPaginationNav = /*#__PURE__*/ Vue.extend({ return info.link }, linkProps(pageNum) { + const props = pluckProps(linkProps, this) const link = this.makeLink(pageNum) - const { - disabled, - exact, - activeClass, - exactActiveClass, - append, - replace, - prefetch, - noPrefetch - } = this - - const props = { - target: this.target || null, - rel: this.rel || null, - disabled, - // The following props are only used if `BLink` detects router - exact, - activeClass, - exactActiveClass, - append, - replace, - // specific prop - prefetch, - noPrefetch - } if (this.useRouter || isObject(link)) { props.to = link } else { props.href = link } - return props }, resolveLink(to = '') { diff --git a/src/components/table/td.js b/src/components/table/td.js index cb6ce671823..0fb91c764dd 100644 --- a/src/components/table/td.js +++ b/src/components/table/td.js @@ -1,4 +1,5 @@ import Vue from '../../utils/vue' +import { isTag } from '../../utils/dom' import { isUndefinedOrNull } from '../../utils/inspect' import { toInteger } from '../../utils/number' import { toString } from '../../utils/string' @@ -6,6 +7,8 @@ import attrsMixin from '../../mixins/attrs' import listenersMixin from '../../mixins/listeners' import normalizeSlotMixin from '../../mixins/normalize-slot' +// --- Utility methods --- + // Parse a rowspan or colspan into a digit (or `null` if < `1` ) const parseSpan = value => { value = toInteger(value, 0) @@ -42,6 +45,7 @@ export const props = { } } +// --- Main component --- // TODO: // In Bootstrap v5, we won't need "sniffing" as table element variants properly inherit // to the child elements, so this can be converted to a functional component @@ -160,7 +164,7 @@ export const BTd = /*#__PURE__*/ Vue.extend({ // Header or footer cells role = 'columnheader' scope = colspan > 0 ? 'colspan' : 'col' - } else if (this.tag === 'th') { + } else if (isTag(this.tag, 'th')) { // th's in tbody role = 'rowheader' scope = rowspan > 0 ? 'rowgroup' : 'row' diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js index f3e40224606..3f62de144e5 100644 --- a/src/components/toast/toast.js +++ b/src/components/toast/toast.js @@ -1,12 +1,15 @@ -import Vue from '../../utils/vue' import { Portal, Wormhole } from 'portal-vue' import BVTransition from '../../utils/bv-transition' +import Vue from '../../utils/vue' +import pluckProps from '../../utils/pluck-props' import { BvEvent } from '../../utils/bv-event.class' import { getComponentConfig } from '../../utils/config' import { requestAF } from '../../utils/dom' import { EVENT_OPTIONS_NO_CAPTURE, eventOnOff } from '../../utils/events' import { mathMax } from '../../utils/math' import { toInteger } from '../../utils/number' +import { pick } from '../../utils/object' +import { isLink } from '../../utils/router' import attrsMixin from '../../mixins/attrs' import idMixin from '../../mixins/id' import listenOnRootMixin from '../../mixins/listen-on-root' @@ -14,7 +17,7 @@ import normalizeSlotMixin from '../../mixins/normalize-slot' import scopedStyleAttrsMixin from '../../mixins/scoped-style-attrs' import { BToaster } from './toaster' import { BButtonClose } from '../button/button-close' -import { BLink } from '../link/link' +import { BLink, props as BLinkProps } from '../link/link' // --- Constants --- @@ -24,6 +27,8 @@ const MIN_DURATION = 1000 // --- Props --- +const linkProps = pick(BLinkProps, ['href', 'to']) + export const props = { id: { // Even though the ID prop is provided by idMixin, we @@ -92,19 +97,12 @@ export const props = { type: [String, Object, Array], default: () => getComponentConfig(NAME, 'bodyClass') }, - href: { - type: String - // default: null - }, - to: { - type: [String, Object] - // default: null - }, static: { // Render the toast in place, rather than in a portal-target type: Boolean, default: false - } + }, + ...linkProps } // @vue/component @@ -385,14 +383,14 @@ export const BToast = /*#__PURE__*/ Vue.extend({ ) } // Toast body - const isLink = this.href || this.to + const link = isLink(this) const $body = h( - isLink ? BLink : 'div', + link ? BLink : 'div', { staticClass: 'toast-body', class: this.bodyClass, - props: isLink ? { to: this.to, href: this.href } : {}, - on: isLink ? { click: this.onLinkClick } : {} + props: link ? pluckProps(linkProps, this) : {}, + on: link ? { click: this.onLinkClick } : {} }, [this.normalizeSlot('default', this.slotScope) || h()] ) diff --git a/src/utils/config-defaults.js b/src/utils/config-defaults.js index 410f382162c..e896f67248c 100644 --- a/src/utils/config-defaults.js +++ b/src/utils/config-defaults.js @@ -183,6 +183,9 @@ export default deepFreeze({ borderVariant: undefined, textVariant: undefined }, + BLink: { + routerComponentName: undefined + }, BListGroupItem: { variant: undefined }, diff --git a/src/utils/dom.js b/src/utils/dom.js index 59c6a0e0b42..b1857122606 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -2,6 +2,7 @@ import { from as arrayFrom } from './array' import { hasWindowSupport, hasDocumentSupport } from './env' import { isFunction, isNull } from './inspect' import { toFloat } from './number' +import { toString } from './string' // --- Constants --- @@ -74,6 +75,9 @@ export const getActiveElement = (excludes = []) => { return activeElement && !excludes.some(el => el === activeElement) ? activeElement : null } +// Returns `true` if a tag's name equals `name` +export const isTag = (tag, name) => toString(tag).toLowerCase() === toString(name).toLowerCase() + // Determine if an HTML element is the currently active element export const isActiveElement = el => isElement(el) && el === getActiveElement() diff --git a/src/utils/router.js b/src/utils/router.js index dc7be512648..521de9fd0bd 100644 --- a/src/utils/router.js +++ b/src/utils/router.js @@ -1,3 +1,4 @@ +import { isTag } from './dom' import { isArray, isNull, isPlainObject, isString, isUndefined } from './inspect' import { keys } from './object' import { toString } from './string' @@ -87,14 +88,25 @@ export const parseQuery = query => { return parsed } -export const isRouterLink = tag => toString(tag).toLowerCase() !== ANCHOR_TAG +export const isLink = props => !!(props.href || props.to) -export const computeTag = ({ to, disabled } = {}, thisOrParent) => { - return thisOrParent.$router && to && !disabled - ? thisOrParent.$nuxt - ? 'nuxt-link' - : 'router-link' - : ANCHOR_TAG +export const isRouterLink = tag => !isTag(tag, ANCHOR_TAG) + +export const computeTag = ({ to, disabled, routerComponentName } = {}, thisOrParent) => { + const hasRouter = thisOrParent.$router + if (!hasRouter || (hasRouter && disabled) || (hasRouter && !to)) { + return ANCHOR_TAG + } + + // TODO: + // Check registered components for existence of user supplied router link component name + // We would need to check PascalCase, kebab-case, and camelCase versions of name: + // const name = routerComponentName + // const names = [name, PascalCase(name), KebabCase(name), CamelCase(name)] + // exists = names.some(name => !!thisOrParent.$options.components[name]) + // And may want to cache the result for performance or we just let the render fail + // if the component is not registered + return routerComponentName || (thisOrParent.$nuxt ? 'nuxt-link' : 'router-link') } export const computeRel = ({ target, rel } = {}) => { From a5df131807d56a5a4513b2148d5f495fe383c737 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 May 2020 08:15:20 +0200 Subject: [PATCH 11/41] chore(deps): update devdependency rollup to ^2.10.1 (#5378) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 80457eb791d..e778d631e12 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "postcss-cli": "^7.1.1", "prettier": "1.14.3", "require-context": "^1.1.0", - "rollup": "^2.10.0", + "rollup": "^2.10.1", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index 82032576856..a2eb3c9eb06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11296,10 +11296,10 @@ rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.0.tgz#73332273fa177cd85c1042659dee6f103761be0d" - integrity sha512-7BmpEfUN9P6esJzWIn3DmR//90mW6YwYB1t3y48LpF8ITpYtL8s1kEirMKqUu44dVH/6a/rs0EuwYVL3FuRDoA== +rollup@^2.10.1: + version "2.10.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.1.tgz#18a97703f0cb6e3a1711a3e4db42021d5290cecc" + integrity sha512-d7e9QM49YAnFjqxTm4HbzDMLX6KeTKEd7UY7uCqP/uVCviXEjyl4e/sGfulTIgvZHVYFS4jXhr+Qugp+WPHkYQ== optionalDependencies: fsevents "~2.1.2" From 844ecda654a2db50d9b84c193f1ab031e291d024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Fri, 15 May 2020 09:39:29 +0200 Subject: [PATCH 12/41] fix: properly handle HTML props render order (closes #5363) (#5365) * fix(jumbatron): fix html props and ensure correct render order * Update html.js * Update visible.js * Update dropdown.js * Update modal.js * Update modal.spec.js * Update carousel-slide.js * Update card.js * Update carousel-slide.js * Update jumbotron.js * Update card-footer.js * Update card-header.js * Update input-group.js * Update progress-bar.js * Update mixin-caption.js * Update mixin-empty.js * Update mixin-thead.js * Unify prop utils * Update props.js * Merge remote-tracking branch 'origin/dev' into fix-jumbatron-html-props * Update dropdown.spec.js * Update dropdown.spec.js * Update dropdown.spec.js * Update dropdown.spec.js * Update modal.spec.js * Update modal.spec.js * Update breadcrumb-link.js * Update card-footer.js * Update card-header.js * Update form-select-option-group.js * Update form-datalist.js * Update test.yml * Merge remote-tracking branch 'origin/dev' into fix-jumbatron-html-props * Update toast.js * Update form-select.js * Update form-radio-check-group.js * Update mixin-thead.js Co-authored-by: Troy Morehouse --- .github/workflows/test.yml | 1 - src/components/avatar/avatar.js | 2 +- src/components/badge/badge.js | 4 +- src/components/breadcrumb/breadcrumb-link.js | 14 +- src/components/button/button.js | 4 +- src/components/card/card-body.js | 6 +- src/components/card/card-footer.js | 22 ++- src/components/card/card-header.js | 21 ++- src/components/card/card.js | 90 +++++---- src/components/carousel/carousel-slide.js | 78 ++++---- src/components/dropdown/dropdown.js | 117 ++++++------ src/components/dropdown/dropdown.spec.js | 46 +++++ .../form-select/form-select-option-group.js | 18 +- src/components/form-select/form-select.js | 79 ++++---- src/components/form/form-datalist.js | 15 +- src/components/input-group/input-group.js | 70 +++---- src/components/jumbotron/jumbotron.js | 80 ++++---- src/components/jumbotron/jumbotron.spec.js | 102 +++++++--- src/components/layout/col.js | 2 +- src/components/layout/row.js | 2 +- src/components/link/link.js | 2 +- src/components/list-group/list-group-item.js | 2 +- src/components/modal/modal.js | 174 +++++++++--------- src/components/modal/modal.spec.js | 37 +++- src/components/nav/nav-item-dropdown.js | 2 +- src/components/navbar/navbar-brand.js | 2 +- src/components/navbar/navbar-nav.js | 4 +- .../pagination-nav/pagination-nav.js | 2 +- src/components/progress/progress-bar.js | 26 ++- src/components/table/helpers/mixin-caption.js | 26 +-- src/components/table/helpers/mixin-empty.js | 46 +++-- src/components/table/helpers/mixin-thead.js | 112 ++++++----- src/components/toast/toast.js | 2 +- src/mixins/form-radio-check-group.js | 17 +- src/utils/copy-props.js | 33 ---- src/utils/html.js | 8 +- src/utils/pluck-props.js | 21 --- src/utils/prefix-prop-name.js | 9 - src/utils/props.js | 44 +++++ .../{copy-props.spec.js => props.spec.js} | 8 +- src/utils/suffix-prop-name.js | 12 -- src/utils/unprefix-prop-name.js | 9 - 42 files changed, 776 insertions(+), 595 deletions(-) delete mode 100644 src/utils/copy-props.js delete mode 100644 src/utils/pluck-props.js delete mode 100644 src/utils/prefix-prop-name.js create mode 100644 src/utils/props.js rename src/utils/{copy-props.spec.js => props.spec.js} (81%) delete mode 100644 src/utils/suffix-prop-name.js delete mode 100644 src/utils/unprefix-prop-name.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 48d75068860..f16a470e4a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -207,4 +207,3 @@ jobs: run: yarn run bundlewatch env: BUNDLEWATCH_GITHUB_TOKEN: "${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}" - CI_BRANCH_BASE: "${{ github.base_ref }}" diff --git a/src/components/avatar/avatar.js b/src/components/avatar/avatar.js index d9974a53b6a..6af4e2b6e20 100644 --- a/src/components/avatar/avatar.js +++ b/src/components/avatar/avatar.js @@ -1,9 +1,9 @@ import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { getComponentConfig } from '../../utils/config' import { isNumber, isString, isUndefinedOrNull } from '../../utils/inspect' import { toFloat } from '../../utils/number' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { isLink } from '../../utils/router' import { BButton } from '../button/button' import { BLink, props as BLinkProps } from '../link/link' diff --git a/src/components/badge/badge.js b/src/components/badge/badge.js index 47941d8e2ba..ab6240fef7d 100644 --- a/src/components/badge/badge.js +++ b/src/components/badge/badge.js @@ -1,8 +1,8 @@ -import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { mergeData } from 'vue-functional-data-merge' +import Vue from '../../utils/vue' import { getComponentConfig } from '../../utils/config' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { isLink } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' diff --git a/src/components/breadcrumb/breadcrumb-link.js b/src/components/breadcrumb/breadcrumb-link.js index 232d1f99d85..e4621bb12c3 100644 --- a/src/components/breadcrumb/breadcrumb-link.js +++ b/src/components/breadcrumb/breadcrumb-link.js @@ -1,10 +1,12 @@ import { mergeData } from 'vue-functional-data-merge' import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { htmlOrText } from '../../utils/html' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { BLink, props as BLinkProps } from '../link/link' +// --- Props --- + export const props = { text: { type: String, @@ -21,17 +23,19 @@ export const props = { ...omit(BLinkProps, ['event', 'routerTag']) } +// --- Main component --- // @vue/component export const BBreadcrumbLink = /*#__PURE__*/ Vue.extend({ name: 'BBreadcrumbLink', functional: true, props, render(h, { props: suppliedProps, data, children }) { - const tag = suppliedProps.active ? 'span' : BLink + const { active } = suppliedProps + const tag = active ? 'span' : BLink - const componentData = { props: pluckProps(props, suppliedProps) } - if (suppliedProps.active) { - componentData.attrs = { 'aria-current': suppliedProps.ariaCurrent } + const componentData = { + attrs: { 'aria-current': active ? suppliedProps.ariaCurrent : null }, + props: pluckProps(props, suppliedProps) } if (!children) { diff --git a/src/components/button/button.js b/src/components/button/button.js index 7704e66bfbf..c98add29382 100644 --- a/src/components/button/button.js +++ b/src/components/button/button.js @@ -1,12 +1,12 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' +import Vue from '../../utils/vue' import KeyCodes from '../../utils/key-codes' -import pluckProps from '../../utils/pluck-props' import { concat } from '../../utils/array' import { getComponentConfig } from '../../utils/config' import { addClass, isTag, removeClass } from '../../utils/dom' import { isBoolean, isEvent, isFunction } from '../../utils/inspect' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { isLink as isLinkStrict } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' diff --git a/src/components/card/card-body.js b/src/components/card/card-body.js index fd66c408c8d..76cbd1a08f0 100644 --- a/src/components/card/card-body.js +++ b/src/components/card/card-body.js @@ -1,8 +1,6 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' -import prefixPropName from '../../utils/prefix-prop-name' -import copyProps from '../../utils/copy-props' -import pluckProps from '../../utils/pluck-props' +import Vue from '../../utils/vue' +import { copyProps, pluckProps, prefixPropName } from '../../utils/props' import cardMixin from '../../mixins/card' import { BCardTitle, props as titleProps } from './card-title' import { BCardSubTitle, props as subTitleProps } from './card-sub-title' diff --git a/src/components/card/card-footer.js b/src/components/card/card-footer.js index cd1cf4550a7..459e2ff9739 100644 --- a/src/components/card/card-footer.js +++ b/src/components/card/card-footer.js @@ -1,11 +1,11 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' - -import prefixPropName from '../../utils/prefix-prop-name' -import copyProps from '../../utils/copy-props' +import Vue from '../../utils/vue' import { htmlOrText } from '../../utils/html' +import { copyProps, prefixPropName } from '../../utils/props' import cardMixin from '../../mixins/card' +// --- Props --- + export const props = { ...copyProps(cardMixin.props, prefixPropName.bind(null, 'footer')), footer: { @@ -22,12 +22,15 @@ export const props = { } } +// --- Main component --- // @vue/component export const BCardFooter = /*#__PURE__*/ Vue.extend({ name: 'BCardFooter', functional: true, props, render(h, { props, data, children }) { + const { footerBgVariant, footerBorderVariant, footerTextVariant } = props + return h( props.footerTag, mergeData(data, { @@ -35,13 +38,14 @@ export const BCardFooter = /*#__PURE__*/ Vue.extend({ class: [ props.footerClass, { - [`bg-${props.footerBgVariant}`]: props.footerBgVariant, - [`border-${props.footerBorderVariant}`]: props.footerBorderVariant, - [`text-${props.footerTextVariant}`]: props.footerTextVariant + [`bg-${footerBgVariant}`]: footerBgVariant, + [`border-${footerBorderVariant}`]: footerBorderVariant, + [`text-${footerTextVariant}`]: footerTextVariant } - ] + ], + domProps: children ? {} : htmlOrText(props.footerHtml, props.footer) }), - children || [h('div', { domProps: htmlOrText(props.footerHtml, props.footer) })] + children ) } }) diff --git a/src/components/card/card-header.js b/src/components/card/card-header.js index cf43ec65956..73f2927464d 100644 --- a/src/components/card/card-header.js +++ b/src/components/card/card-header.js @@ -1,10 +1,11 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' -import prefixPropName from '../../utils/prefix-prop-name' -import copyProps from '../../utils/copy-props' +import Vue from '../../utils/vue' import { htmlOrText } from '../../utils/html' +import { copyProps, prefixPropName } from '../../utils/props' import cardMixin from '../../mixins/card' +// --- Props --- + export const props = { ...copyProps(cardMixin.props, prefixPropName.bind(null, 'header')), header: { @@ -21,12 +22,15 @@ export const props = { } } +// --- Main component --- // @vue/component export const BCardHeader = /*#__PURE__*/ Vue.extend({ name: 'BCardHeader', functional: true, props, render(h, { props, data, children }) { + const { headerBgVariant, headerBorderVariant, headerTextVariant } = props + return h( props.headerTag, mergeData(data, { @@ -34,13 +38,14 @@ export const BCardHeader = /*#__PURE__*/ Vue.extend({ class: [ props.headerClass, { - [`bg-${props.headerBgVariant}`]: props.headerBgVariant, - [`border-${props.headerBorderVariant}`]: props.headerBorderVariant, - [`text-${props.headerTextVariant}`]: props.headerTextVariant + [`bg-${headerBgVariant}`]: headerBgVariant, + [`border-${headerBorderVariant}`]: headerBorderVariant, + [`text-${headerTextVariant}`]: headerTextVariant } - ] + ], + domProps: children ? {} : htmlOrText(props.headerHtml, props.header) }), - children || [h('div', { domProps: htmlOrText(props.headerHtml, props.header) })] + children ) } }) diff --git a/src/components/card/card.js b/src/components/card/card.js index 071de824ae4..aff8496f823 100644 --- a/src/components/card/card.js +++ b/src/components/card/card.js @@ -1,10 +1,8 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' -import prefixPropName from '../../utils/prefix-prop-name' -import unPrefixPropName from '../../utils/unprefix-prop-name' -import copyProps from '../../utils/copy-props' -import pluckProps from '../../utils/pluck-props' +import Vue from '../../utils/vue' +import { htmlOrText } from '../../utils/html' import { hasNormalizedSlot, normalizeSlot } from '../../utils/normalize-slot' +import { copyProps, pluckProps, prefixPropName, unprefixPropName } from '../../utils/props' import cardMixin from '../../mixins/card' import { BCardBody, props as bodyProps } from './card-body' import { BCardHeader, props as headerProps } from './card-header' @@ -36,49 +34,68 @@ export const BCard = /*#__PURE__*/ Vue.extend({ functional: true, props, render(h, { props, data, slots, scopedSlots }) { - const $slots = slots() - // Vue < 2.6.x may return undefined for scopedSlots + const { + imgLeft, + imgRight, + imgStart, + imgEnd, + header, + headerHtml, + footer, + footerHtml, + align, + textVariant, + bgVariant, + borderVariant + } = props const $scopedSlots = scopedSlots || {} + const $slots = slots() + const slotScope = {} - // Create placeholder elements for each section - let imgFirst = h() - let header = h() - let content = h() - let footer = h() - let imgLast = h() - + let $imgFirst = h() + let $imgLast = h() if (props.imgSrc) { - const img = h(BCardImg, { - props: pluckProps(cardImgProps, props, unPrefixPropName.bind(null, 'img')) + const $img = h(BCardImg, { + props: pluckProps(cardImgProps, props, unprefixPropName.bind(null, 'img')) }) + if (props.imgBottom) { - imgLast = img + $imgLast = $img } else { - imgFirst = img + $imgFirst = $img } } - if (props.header || props.headerHtml || hasNormalizedSlot('header', $scopedSlots, $slots)) { - header = h( + let $header = h() + const hasHeaderSlot = hasNormalizedSlot('header', $scopedSlots, $slots) + if (hasHeaderSlot || header || headerHtml) { + $header = h( BCardHeader, - { props: pluckProps(headerProps, props) }, - normalizeSlot('header', {}, $scopedSlots, $slots) + { + props: pluckProps(headerProps, props), + domProps: hasHeaderSlot ? {} : htmlOrText(headerHtml, header) + }, + normalizeSlot('header', slotScope, $scopedSlots, $slots) ) } - content = normalizeSlot('default', {}, $scopedSlots, $slots) || [] + let $content = normalizeSlot('default', slotScope, $scopedSlots, $slots) + + // Wrap content in when `noBody` prop set if (!props.noBody) { - // Wrap content in card-body - content = [h(BCardBody, { props: pluckProps(bodyProps, props) }, [...content])] + $content = h(BCardBody, { props: pluckProps(bodyProps, props) }, $content) } - if (props.footer || props.footerHtml || hasNormalizedSlot('footer', $scopedSlots, $slots)) { - footer = h( + let $footer = h() + const hasFooterSlot = hasNormalizedSlot('footer', $scopedSlots, $slots) + if (hasFooterSlot || footer || footerHtml) { + $footer = h( BCardFooter, { - props: pluckProps(footerProps, props) + props: pluckProps(footerProps, props), + domProps: hasHeaderSlot ? {} : htmlOrText(footerHtml, footer) }, - normalizeSlot('footer', {}, $scopedSlots, $slots) + normalizeSlot('footer', slotScope, $scopedSlots, $slots) ) } @@ -87,16 +104,15 @@ export const BCard = /*#__PURE__*/ Vue.extend({ mergeData(data, { staticClass: 'card', class: { - 'flex-row': props.imgLeft || props.imgStart, - 'flex-row-reverse': - (props.imgRight || props.imgEnd) && !(props.imgLeft || props.imgStart), - [`text-${props.align}`]: props.align, - [`bg-${props.bgVariant}`]: props.bgVariant, - [`border-${props.borderVariant}`]: props.borderVariant, - [`text-${props.textVariant}`]: props.textVariant + 'flex-row': imgLeft || imgStart, + 'flex-row-reverse': (imgRight || imgEnd) && !(imgLeft || imgStart), + [`text-${align}`]: align, + [`bg-${bgVariant}`]: bgVariant, + [`border-${borderVariant}`]: borderVariant, + [`text-${textVariant}`]: textVariant } }), - [imgFirst, header, ...content, footer, imgLast] + [$imgFirst, $header, $content, $footer, $imgLast] ) } }) diff --git a/src/components/carousel/carousel-slide.js b/src/components/carousel/carousel-slide.js index 0f2bdb15181..e5953afa83c 100644 --- a/src/components/carousel/carousel-slide.js +++ b/src/components/carousel/carousel-slide.js @@ -1,11 +1,14 @@ import Vue from '../../utils/vue' -import idMixin from '../../mixins/id' -import normalizeSlotMixin from '../../mixins/normalize-slot' import { hasTouchSupport } from '../../utils/env' import { htmlOrText } from '../../utils/html' +import { pluckProps, unprefixPropName } from '../../utils/props' +import idMixin from '../../mixins/id' +import normalizeSlotMixin from '../../mixins/normalize-slot' import { BImg } from '../image/img' -export const props = { +// --- Props --- + +const imgProps = { imgSrc: { type: String // default: undefined @@ -29,7 +32,11 @@ export const props = { imgBlankColor: { type: String, default: 'transparent' - }, + } +} + +export const props = { + ...imgProps, contentVisibleUp: { type: String }, @@ -62,6 +69,7 @@ export const props = { } } +// --- Main component --- // @vue/component export const BCarouselSlide = /*#__PURE__*/ Vue.extend({ name: 'BCarouselSlide', @@ -94,55 +102,51 @@ export const BCarouselSlide = /*#__PURE__*/ Vue.extend({ } }, render(h) { - const noDrag = !this.bvCarousel.noTouch && hasTouchSupport + let $img = this.normalizeSlot('img') + if (!$img && (this.imgSrc || this.imgBlank)) { + const on = {} + // Touch support event handler + /* istanbul ignore if: difficult to test in JSDOM */ + if (!this.bvCarousel.noTouch && hasTouchSupport) { + on.dragstart = evt => { + evt.preventDefault() + } + } - let img = this.normalizeSlot('img') - if (!img && (this.imgSrc || this.imgBlank)) { - img = h(BImg, { + $img = h(BImg, { props: { - fluidGrow: true, - block: true, - src: this.imgSrc, - blank: this.imgBlank, - blankColor: this.imgBlankColor, + ...pluckProps(imgProps, this.$props, unprefixPropName.bind(null, 'img')), width: this.computedWidth, height: this.computedHeight, - alt: this.imgAlt + fluidGrow: true, + block: true }, - // Touch support event handler - on: noDrag - ? /* istanbul ignore next */ { - dragstart /* istanbul ignore next */: e => { - /* istanbul ignore next: difficult to test in JSDOM */ - e.preventDefault() - } - } - : {} + on }) } - if (!img) { - img = h() - } - let content = h() - - const contentChildren = [ + const $contentChildren = [ + // Caption this.caption || this.captionHtml - ? h(this.captionTag, { - domProps: htmlOrText(this.captionHtml, this.caption) - }) + ? h(this.captionTag, { domProps: htmlOrText(this.captionHtml, this.caption) }) : false, + // Text this.text || this.textHtml ? h(this.textTag, { domProps: htmlOrText(this.textHtml, this.text) }) : false, + // Children this.normalizeSlot('default') || false ] - if (contentChildren.some(Boolean)) { - content = h( + let $content = h() + if ($contentChildren.some(Boolean)) { + $content = h( this.contentTag, - { staticClass: 'carousel-caption', class: this.contentClasses }, - contentChildren.map(i => i || h()) + { + staticClass: 'carousel-caption', + class: this.contentClasses + }, + $contentChildren.map($child => $child || h()) ) } @@ -153,7 +157,7 @@ export const BCarouselSlide = /*#__PURE__*/ Vue.extend({ style: { background: this.background || this.bvCarousel.background || null }, attrs: { id: this.safeId(), role: 'listitem' } }, - [img, content] + [$img, $content] ) } }) diff --git a/src/components/dropdown/dropdown.js b/src/components/dropdown/dropdown.js index 9490b05e490..611223f4c3a 100644 --- a/src/components/dropdown/dropdown.js +++ b/src/components/dropdown/dropdown.js @@ -1,14 +1,18 @@ import Vue from '../../utils/vue' import { arrayIncludes } from '../../utils/array' -import { stripTags } from '../../utils/html' import { getComponentConfig } from '../../utils/config' -import idMixin from '../../mixins/id' +import { htmlOrText } from '../../utils/html' import dropdownMixin from '../../mixins/dropdown' +import idMixin from '../../mixins/id' import normalizeSlotMixin from '../../mixins/normalize-slot' import { BButton } from '../button/button' +// --- Constants --- + const NAME = 'BDropdown' +// --- Props --- + export const props = { text: { // Button label @@ -20,14 +24,14 @@ export const props = { type: String // default: undefined }, - size: { - type: String, - default: () => getComponentConfig(NAME, 'size') - }, variant: { type: String, default: () => getComponentConfig(NAME, 'variant') }, + size: { + type: String, + default: () => getComponentConfig(NAME, 'size') + }, block: { type: Boolean, default: false @@ -89,6 +93,7 @@ export const props = { } } +// --- Main component --- // @vue/component export const BDropdown = /*#__PURE__*/ Vue.extend({ name: NAME, @@ -96,6 +101,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({ props, computed: { dropdownClasses() { + const { block, split, boundary } = this return [ this.directionClass, { @@ -103,14 +109,14 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({ // The 'btn-group' class is required in `split` mode for button alignment // It needs also to be applied when `block` is disabled to allow multiple // dropdowns to be aligned one line - 'btn-group': this.split || !this.block, + 'btn-group': split || !block, // When `block` is enabled and we are in `split` mode the 'd-flex' class // needs to be applied to allow the buttons to stretch to full width - 'd-flex': this.block && this.split, + 'd-flex': block && split, // Position `static` is needed to allow menu to "breakout" of the `scrollParent` // boundaries when boundary is anything other than `scrollParent` // See: https://github.com/twbs/bootstrap/issues/24251#issuecomment-341413786 - 'position-static': this.boundary !== 'scrollParent' || !this.boundary + 'position-static': boundary !== 'scrollParent' || !boundary } ] }, @@ -124,92 +130,99 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({ ] }, toggleClasses() { + const { split } = this return [ this.toggleClass, { - 'dropdown-toggle-split': this.split, - 'dropdown-toggle-no-caret': this.noCaret && !this.split + 'dropdown-toggle-split': split, + 'dropdown-toggle-no-caret': this.noCaret && !split } ] } }, render(h) { - let split = h() - const buttonContent = this.normalizeSlot('button-content') || this.html || stripTags(this.text) - if (this.split) { + const { variant, size, block, disabled, split, role } = this + const commonProps = { variant, size, block, disabled } + + const $buttonContent = this.normalizeSlot('button-content') + const buttonContentProps = this.hasNormalizedSlot('button-content') + ? {} + : htmlOrText(this.html, this.text) + + let $split = h() + if (split) { + const { splitTo, splitHref, splitButtonType } = this const btnProps = { - variant: this.splitVariant || this.variant, - size: this.size, - block: this.block, - disabled: this.disabled + ...commonProps, + variant: this.splitVariant || this.variant } - // We add these as needed due to router-link issues with defined property with undefined/null values - if (this.splitTo) { - btnProps.to = this.splitTo - } else if (this.splitHref) { - btnProps.href = this.splitHref - } else if (this.splitButtonType) { - btnProps.type = this.splitButtonType + // We add these as needed due to issues with + // defined property with `undefined`/`null` values + if (splitTo) { + btnProps.to = splitTo + } else if (splitHref) { + btnProps.href = splitHref + } else if (splitButtonType) { + btnProps.type = splitButtonType } - split = h( + $split = h( BButton, { - ref: 'button', - props: btnProps, class: this.splitClass, - attrs: { - id: this.safeId('_BV_button_') - }, - on: { - click: this.onSplitClick - } + attrs: { id: this.safeId('_BV_button_') }, + props: btnProps, + domProps: buttonContentProps, + on: { click: this.onSplitClick }, + ref: 'button' }, - [buttonContent] + [$buttonContent] ) } - const toggle = h( + + const $toggle = h( BButton, { - ref: 'toggle', staticClass: 'dropdown-toggle', class: this.toggleClasses, - props: { - tag: this.toggleTag, - variant: this.variant, - size: this.size, - block: this.block && !this.split, - disabled: this.disabled - }, attrs: { id: this.safeId('_BV_toggle_'), 'aria-haspopup': 'true', 'aria-expanded': this.visible ? 'true' : 'false' }, + props: { + ...commonProps, + tag: this.toggleTag, + block: block && !split + }, + domProps: split ? {} : buttonContentProps, on: { mousedown: this.onMousedown, click: this.toggle, keydown: this.toggle // Handle ENTER, SPACE and DOWN - } + }, + ref: 'toggle' }, - [this.split ? h('span', { class: ['sr-only'] }, [this.toggleText]) : buttonContent] + [split ? h('span', { class: ['sr-only'] }, [this.toggleText]) : $buttonContent] ) - const menu = h( + + const $menu = h( 'ul', { - ref: 'menu', staticClass: 'dropdown-menu', class: this.menuClasses, attrs: { - role: this.role, + role, tabindex: '-1', - 'aria-labelledby': this.safeId(this.split ? '_BV_button_' : '_BV_toggle_') + 'aria-labelledby': this.safeId(split ? '_BV_button_' : '_BV_toggle_') }, on: { keydown: this.onKeydown // Handle UP, DOWN and ESC - } + }, + ref: 'menu' }, !this.lazy || this.visible ? this.normalizeSlot('default', { hide: this.hide }) : [h()] ) + return h( 'div', { @@ -217,7 +230,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({ class: this.dropdownClasses, attrs: { id: this.safeId() } }, - [split, toggle, menu] + [$split, $toggle, $menu] ) } }) diff --git a/src/components/dropdown/dropdown.spec.js b/src/components/dropdown/dropdown.spec.js index 651199f70e3..4ed362e18ba 100644 --- a/src/components/dropdown/dropdown.spec.js +++ b/src/components/dropdown/dropdown.spec.js @@ -199,6 +199,52 @@ describe('dropdown', () => { wrapper.destroy() }) + it('renders button-content slot inside toggle button', async () => { + const wrapper = mount(BDropdown, { + attachTo: createContainer(), + slots: { + 'button-content': 'foobar' + } + }) + + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.vm).toBeDefined() + + expect(wrapper.findAll('button').length).toBe(1) + expect(wrapper.findAll('.dropdown-toggle').length).toBe(1) + const $toggle = wrapper.find('.dropdown-toggle') + expect($toggle.text()).toEqual('foobar') + + wrapper.destroy() + }) + + it('renders button-content slot inside split button', async () => { + const wrapper = mount(BDropdown, { + attachTo: createContainer(), + propsData: { + split: true + }, + slots: { + 'button-content': 'foobar' + } + }) + + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.vm).toBeDefined() + + expect(wrapper.findAll('button').length).toBe(2) + const $buttons = wrapper.findAll('button') + const $split = $buttons.at(0) + const $toggle = $buttons.at(1) + + expect($split.text()).toEqual('foobar') + expect($toggle.classes()).toContain('dropdown-toggle') + // Toggle has `sr-only` hidden text + expect($toggle.text()).toEqual('Toggle Dropdown') + + wrapper.destroy() + }) + it('does not render default slot inside menu when prop lazy set', async () => { const wrapper = mount(BDropdown, { attachTo: createContainer(), diff --git a/src/components/form-select/form-select-option-group.js b/src/components/form-select/form-select-option-group.js index ba2c8156d02..6c3e0622fcc 100644 --- a/src/components/form-select/form-select-option-group.js +++ b/src/components/form-select/form-select-option-group.js @@ -15,15 +15,19 @@ const BFormSelectOptionGroup = /*#__PURE__*/ Vue.extend({ } }, render(h) { + const $options = this.formOptions.map((option, index) => { + const { value, text, html, disabled } = option + + return h(BFormSelectOption, { + attrs: { value, disabled }, + domProps: htmlOrText(html, text), + key: `option_${index}` + }) + }) + return h('optgroup', { attrs: { label: this.label } }, [ this.normalizeSlot('first'), - this.formOptions.map((option, index) => - h(BFormSelectOption, { - props: { value: option.value, disabled: option.disabled }, - domProps: htmlOrText(option.html, option.text), - key: `option_${index}_opt` - }) - ), + $options, this.normalizeSlot('default') ]) } diff --git a/src/components/form-select/form-select.js b/src/components/form-select/form-select.js index 81342a5988d..e56f84ef4a2 100644 --- a/src/components/form-select/form-select.js +++ b/src/components/form-select/form-select.js @@ -2,11 +2,11 @@ import Vue from '../../utils/vue' import { from as arrayFrom, isArray } from '../../utils/array' import { attemptBlur, attemptFocus } from '../../utils/dom' import { htmlOrText } from '../../utils/html' -import idMixin from '../../mixins/id' +import formCustomMixin from '../../mixins/form-custom' import formMixin from '../../mixins/form' import formSizeMixin from '../../mixins/form-size' import formStateMixin from '../../mixins/form-state' -import formCustomMixin from '../../mixins/form-custom' +import idMixin from '../../mixins/id' import normalizeSlotMixin from '../../mixins/normalize-slot' import optionsMixin from './helpers/mixin-options' import { BFormSelectOption } from './form-select-option' @@ -88,61 +88,54 @@ export const BFormSelect = /*#__PURE__*/ Vue.extend({ }, blur() { attemptBlur(this.$refs.input) + }, + onChange(evt) { + const { target } = evt + const selectedVal = arrayFrom(target.options) + .filter(o => o.selected) + .map(o => ('_value' in o ? o._value : o.value)) + this.localValue = target.multiple ? selectedVal : selectedVal[0] + this.$nextTick(() => { + this.$emit('change', this.localValue) + }) } }, render(h) { + const { name, disabled, required, computedSelectSize: size, localValue: value } = this + + const $options = this.formOptions.map((option, index) => { + const { value, label, options, disabled } = option + const key = `option_${index}` + + return isArray(options) + ? h(BFormSelectOptionGroup, { props: { label, options }, key }) + : h(BFormSelectOption, { + props: { value, disabled }, + domProps: htmlOrText(option.html, option.text), + key + }) + }) + return h( 'select', { - ref: 'input', class: this.inputClass, - directives: [ - { - name: 'model', - rawName: 'v-model', - value: this.localValue, - expression: 'localValue' - } - ], attrs: { id: this.safeId(), - name: this.name, + name, form: this.form || null, multiple: this.multiple || null, - size: this.computedSelectSize, - disabled: this.disabled, - required: this.required, - 'aria-required': this.required ? 'true' : null, + size, + disabled, + required, + 'aria-required': required ? 'true' : null, 'aria-invalid': this.computedAriaInvalid }, - on: { - change: evt => { - const target = evt.target - const selectedVal = arrayFrom(target.options) - .filter(o => o.selected) - .map(o => ('_value' in o ? o._value : o.value)) - this.localValue = target.multiple ? selectedVal : selectedVal[0] - this.$nextTick(() => { - this.$emit('change', this.localValue) - }) - } - } + on: { change: this.onChange }, + directives: [{ name: 'model', value }], + ref: 'input' }, - [ - this.normalizeSlot('first'), - this.formOptions.map((option, index) => { - const key = `option_${index}_opt` - const options = option.options - return isArray(options) - ? h(BFormSelectOptionGroup, { props: { label: option.label, options }, key }) - : h(BFormSelectOption, { - props: { value: option.value, disabled: option.disabled }, - domProps: htmlOrText(option.html, option.text), - key - }) - }), - this.normalizeSlot('default') - ] + [this.normalizeSlot('first'), $options, this.normalizeSlot('default')] ) } }) diff --git a/src/components/form/form-datalist.js b/src/components/form/form-datalist.js index c9c17f18432..6cabccdcf42 100644 --- a/src/components/form/form-datalist.js +++ b/src/components/form/form-datalist.js @@ -1,7 +1,7 @@ import Vue from '../../utils/vue' +import { htmlOrText } from '../../utils/html' import formOptionsMixin from '../../mixins/form-options' import normalizeSlotMixin from '../../mixins/normalize-slot' -import { htmlOrText } from '../../utils/html' // @vue/component export const BFormDatalist = /*#__PURE__*/ Vue.extend({ @@ -14,13 +14,16 @@ export const BFormDatalist = /*#__PURE__*/ Vue.extend({ } }, render(h) { - const options = this.formOptions.map((option, index) => { + const $options = this.formOptions.map((option, index) => { + const { value, text, html, disabled } = option + return h('option', { - key: `option_${index}_opt`, - attrs: { disabled: option.disabled }, - domProps: { ...htmlOrText(option.html, option.text), value: option.value } + attrs: { value, disabled }, + domProps: htmlOrText(html, text), + key: `option_${index}` }) }) - return h('datalist', { attrs: { id: this.id } }, [options, this.normalizeSlot('default')]) + + return h('datalist', { attrs: { id: this.id } }, [$options, this.normalizeSlot('default')]) } }) diff --git a/src/components/input-group/input-group.js b/src/components/input-group/input-group.js index 8f566bb7874..f5a97f0f47e 100644 --- a/src/components/input-group/input-group.js +++ b/src/components/input-group/input-group.js @@ -1,14 +1,18 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' +import Vue from '../../utils/vue' import { getComponentConfig } from '../../utils/config' import { htmlOrText } from '../../utils/html' import { hasNormalizedSlot, normalizeSlot } from '../../utils/normalize-slot' -import { BInputGroupPrepend } from './input-group-prepend' import { BInputGroupAppend } from './input-group-append' +import { BInputGroupPrepend } from './input-group-prepend' import { BInputGroupText } from './input-group-text' +// --- Constants --- + const NAME = 'BInputGroup' +// --- Props --- + export const props = { id: { type: String @@ -35,67 +39,49 @@ export const props = { } } +// --- Main component --- // @vue/component export const BInputGroup = /*#__PURE__*/ Vue.extend({ name: NAME, functional: true, props, render(h, { props, data, slots, scopedSlots }) { - const $slots = slots() + const { prepend, prependHtml, append, appendHtml, size } = props const $scopedSlots = scopedSlots || {} + const $slots = slots() + const slotScope = {} - const childNodes = [] - - // Prepend prop/slot - if (props.prepend || props.prependHtml || hasNormalizedSlot('prepend', $scopedSlots, $slots)) { - childNodes.push( - h(BInputGroupPrepend, [ - // Prop - props.prepend || props.prependHtml - ? h(BInputGroupText, { domProps: htmlOrText(props.prependHtml, props.prepend) }) - : h(), - // Slot - normalizeSlot('prepend', {}, $scopedSlots, $slots) || h() - ]) - ) - } else { - childNodes.push(h()) - } - - // Default slot - if (hasNormalizedSlot('default', $scopedSlots, $slots)) { - childNodes.push(...normalizeSlot('default', {}, $scopedSlots, $slots)) - } else { - childNodes.push(h()) + let $prepend = h() + const hasPrependSlot = hasNormalizedSlot('prepend', $scopedSlots, $slots) + if (hasPrependSlot || prepend || prependHtml) { + $prepend = h(BInputGroupPrepend, [ + hasPrependSlot + ? normalizeSlot('prepend', slotScope, $scopedSlots, $slots) + : h(BInputGroupText, { domProps: htmlOrText(prependHtml, prepend) }) + ]) } - // Append prop - if (props.append || props.appendHtml || hasNormalizedSlot('append', $scopedSlots, $slots)) { - childNodes.push( - h(BInputGroupAppend, [ - // prop - props.append || props.appendHtml - ? h(BInputGroupText, { domProps: htmlOrText(props.appendHtml, props.append) }) - : h(), - // Slot - normalizeSlot('append', {}, $scopedSlots, $slots) || h() - ]) - ) - } else { - childNodes.push(h()) + let $append = h() + const hasAppendSlot = hasNormalizedSlot('append', $scopedSlots, $slots) + if (hasAppendSlot || append || appendHtml) { + $append = h(BInputGroupAppend, [ + hasAppendSlot + ? normalizeSlot('append', slotScope, $scopedSlots, $slots) + : h(BInputGroupText, { domProps: htmlOrText(appendHtml, append) }) + ]) } return h( props.tag, mergeData(data, { staticClass: 'input-group', - class: { [`input-group-${props.size}`]: props.size }, + class: { [`input-group-${size}`]: size }, attrs: { id: props.id || null, role: 'group' } }), - childNodes + [$prepend, normalizeSlot('default', slotScope, $scopedSlots, $slots), $append] ) } }) diff --git a/src/components/jumbotron/jumbotron.js b/src/components/jumbotron/jumbotron.js index bcfc81bffb5..9bcc585e168 100644 --- a/src/components/jumbotron/jumbotron.js +++ b/src/components/jumbotron/jumbotron.js @@ -1,12 +1,16 @@ import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' import { getComponentConfig } from '../../utils/config' -import { stripTags } from '../../utils/html' +import { htmlOrText } from '../../utils/html' import { hasNormalizedSlot, normalizeSlot } from '../../utils/normalize-slot' import { BContainer } from '../layout/container' +// --- Constants --- + const NAME = 'BJumbotron' +// --- Props --- + export const props = { fluid: { type: Boolean, @@ -62,70 +66,66 @@ export const props = { } } +// --- Main component --- // @vue/component export const BJumbotron = /*#__PURE__*/ Vue.extend({ name: NAME, functional: true, props, render(h, { props, data, slots, scopedSlots }) { - // The order of the conditionals matter. - // We are building the component markup in order. - let childNodes = [] - const $slots = slots() + const { header, headerHtml, lead, leadHtml, textVariant, bgVariant, borderVariant } = props const $scopedSlots = scopedSlots || {} + const $slots = slots() + const slotScope = {} - // Header - if (props.header || hasNormalizedSlot('header', $scopedSlots, $slots) || props.headerHtml) { - childNodes.push( - h( - props.headerTag, - { - class: { - [`display-${props.headerLevel}`]: props.headerLevel - } - }, - normalizeSlot('header', {}, $scopedSlots, $slots) || - props.headerHtml || - stripTags(props.header) - ) + let $header = h() + const hasHeaderSlot = hasNormalizedSlot('header', $scopedSlots, $slots) + if (hasHeaderSlot || header || headerHtml) { + const { headerLevel } = props + + $header = h( + props.headerTag, + { + class: { [`display-${headerLevel}`]: headerLevel }, + domProps: hasHeaderSlot ? {} : htmlOrText(headerHtml, header) + }, + normalizeSlot('header', slotScope, $scopedSlots, $slots) ) } - // Lead - if (props.lead || hasNormalizedSlot('lead', $scopedSlots, $slots) || props.leadHtml) { - childNodes.push( - h( - props.leadTag, - { staticClass: 'lead' }, - normalizeSlot('lead', {}, $scopedSlots, $slots) || props.leadHtml || stripTags(props.lead) - ) + let $lead = h() + const hasLeadSlot = hasNormalizedSlot('lead', $scopedSlots, $slots) + if (hasLeadSlot || lead || leadHtml) { + $lead = h( + props.leadTag, + { + staticClass: 'lead', + domProps: hasLeadSlot ? {} : htmlOrText(leadHtml, lead) + }, + normalizeSlot('lead', slotScope, $scopedSlots, $slots) ) } - // Default slot - if (hasNormalizedSlot('default', $scopedSlots, $slots)) { - childNodes.push(normalizeSlot('default', {}, $scopedSlots, $slots)) - } + let $children = [$header, $lead, normalizeSlot('default', slotScope, $scopedSlots, $slots)] - // If fluid, wrap content in a container/container-fluid + // If fluid, wrap content in a container if (props.fluid) { - // Children become a child of a container - childNodes = [h(BContainer, { props: { fluid: props.containerFluid } }, childNodes)] + $children = [h(BContainer, { props: { fluid: props.containerFluid } }, $children)] } - // Return the jumbotron + return h( props.tag, mergeData(data, { staticClass: 'jumbotron', class: { 'jumbotron-fluid': props.fluid, - [`text-${props.textVariant}`]: props.textVariant, - [`bg-${props.bgVariant}`]: props.bgVariant, - [`border-${props.borderVariant}`]: props.borderVariant, - border: props.borderVariant + [`text-${textVariant}`]: textVariant, + [`bg-${bgVariant}`]: bgVariant, + [`border-${borderVariant}`]: borderVariant, + border: borderVariant } }), - childNodes + $children ) } }) diff --git a/src/components/jumbotron/jumbotron.spec.js b/src/components/jumbotron/jumbotron.spec.js index 194d3b781b5..1165061a204 100644 --- a/src/components/jumbotron/jumbotron.spec.js +++ b/src/components/jumbotron/jumbotron.spec.js @@ -13,7 +13,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('renders with custom root element when props tag is set', async () => { + it('renders with custom root element when prop "tag" is set', async () => { const wrapper = mount(BJumbotron, { propsData: { tag: 'article' @@ -28,7 +28,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('has border when prop border-variant is set', async () => { + it('has border when prop "border-variant" is set', async () => { const wrapper = mount(BJumbotron, { propsData: { borderVariant: 'danger' @@ -44,7 +44,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('has background variant when prop bg-variant is set', async () => { + it('has background variant when prop "bg-variant" is set', async () => { const wrapper = mount(BJumbotron, { propsData: { bgVariant: 'info' @@ -59,7 +59,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('has text variant when prop text-variant is set', async () => { + it('has text variant when prop "text-variant" is set', async () => { const wrapper = mount(BJumbotron, { propsData: { textVariant: 'primary' @@ -90,7 +90,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('renders default slot content inside container when fluid prop set', async () => { + it('renders default slot content inside container when "fluid" prop set', async () => { const wrapper = mount(BJumbotron, { propsData: { fluid: true @@ -113,7 +113,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('renders default slot content inside container-fluid when fluid prop and container-fluid set', async () => { + it('renders default slot content inside ".container-fluid" when props "fluid" and "container-fluid" set', async () => { const wrapper = mount(BJumbotron, { propsData: { fluid: true, @@ -138,7 +138,7 @@ describe('jumbotron', () => { wrapper.destroy() }) - it('renders header lead and content when using props', async () => { + it('renders header and lead content by props', async () => { const wrapper = mount(BJumbotron, { propsData: { header: 'foo', @@ -153,25 +153,35 @@ describe('jumbotron', () => { expect(wrapper.classes()).toContain('jumbotron') expect(wrapper.classes().length).toBe(1) expect(wrapper.findAll('h1').length).toBe(1) - expect(wrapper.find('h1').classes()).toContain('display-3') - expect(wrapper.find('h1').classes().length).toBe(1) - expect(wrapper.find('h1').text()).toEqual('foo') expect(wrapper.findAll('p').length).toBe(1) - expect(wrapper.find('p').classes()).toContain('lead') - expect(wrapper.find('p').classes().length).toBe(1) - expect(wrapper.find('p').text()).toEqual('bar') expect(wrapper.findAll('span').length).toBe(1) - expect(wrapper.find('span').text()).toEqual('baz') expect(wrapper.find('.jumbotron > h1 + p + span').exists()).toBe(true) + const $header = wrapper.find('h1') + expect($header.classes()).toContain('display-3') + expect($header.classes().length).toBe(1) + expect($header.text()).toEqual('foo') + + const $lead = wrapper.find('p') + expect($lead.classes()).toContain('lead') + expect($lead.classes().length).toBe(1) + expect($lead.text()).toEqual('bar') + + expect(wrapper.find('span').text()).toEqual('baz') + wrapper.destroy() }) - it('renders header lead and content when using slots', async () => { + it('renders header and lead content by html props', async () => { const wrapper = mount(BJumbotron, { - slots: { + propsData: { + // We also pass non-html props to ensure html props have precedence header: 'foo', + headerHtml: 'baz', lead: 'bar', + leadHtml: 'bat' + }, + slots: { default: 'baz' } }) @@ -180,17 +190,65 @@ describe('jumbotron', () => { expect(wrapper.classes()).toContain('jumbotron') expect(wrapper.classes().length).toBe(1) expect(wrapper.findAll('h1').length).toBe(1) - expect(wrapper.find('h1').classes()).toContain('display-3') - expect(wrapper.find('h1').classes().length).toBe(1) - expect(wrapper.find('h1').text()).toEqual('foo') expect(wrapper.findAll('p').length).toBe(1) - expect(wrapper.find('p').classes()).toContain('lead') - expect(wrapper.find('p').classes().length).toBe(1) - expect(wrapper.find('p').text()).toEqual('bar') expect(wrapper.findAll('span').length).toBe(1) + expect(wrapper.find('.jumbotron > h1 + p + span').exists()).toBe(true) + + const $header = wrapper.find('h1') + expect($header.classes()).toContain('display-3') + expect($header.classes().length).toBe(1) + expect($header.find('strong').exists()).toBe(true) + expect($header.text()).toEqual('baz') + + const $lead = wrapper.find('p') + expect($lead.classes()).toContain('lead') + expect($lead.classes().length).toBe(1) + expect($lead.find('strong').exists()).toBe(true) + expect($lead.text()).toEqual('bat') + expect(wrapper.find('span').text()).toEqual('baz') + + wrapper.destroy() + }) + + it('renders header and lead content by slots', async () => { + const wrapper = mount(BJumbotron, { + propsData: { + // We also pass as props to ensure slots have precedence + header: 'foo', + headerHtml: 'baz', + lead: 'bar', + leadHtml: 'bat' + }, + slots: { + default: 'baz', + header: 'foo', + lead: 'bar' + } + }) + + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.classes()).toContain('jumbotron') + expect(wrapper.classes().length).toBe(1) + expect(wrapper.findAll('h1').length).toBe(1) + expect(wrapper.findAll('p').length).toBe(1) + expect(wrapper.findAll('span').length).toBe(1) expect(wrapper.find('.jumbotron > h1 + p + span').exists()).toBe(true) + const $header = wrapper.find('h1') + expect($header.classes()).toContain('display-3') + expect($header.classes().length).toBe(1) + expect($header.find('small').exists()).toBe(true) + expect($header.text()).toEqual('foo') + + const $lead = wrapper.find('p') + expect($lead.classes()).toContain('lead') + expect($lead.classes().length).toBe(1) + expect($lead.find('small').exists()).toBe(true) + expect($lead.text()).toEqual('bar') + + expect(wrapper.find('span').text()).toEqual('baz') + wrapper.destroy() }) }) diff --git a/src/components/layout/col.js b/src/components/layout/col.js index c10b1de2beb..ffdc1aad1aa 100644 --- a/src/components/layout/col.js +++ b/src/components/layout/col.js @@ -1,11 +1,11 @@ import { mergeData } from 'vue-functional-data-merge' import identity from '../../utils/identity' import memoize from '../../utils/memoize' -import suffixPropName from '../../utils/suffix-prop-name' import { arrayIncludes } from '../../utils/array' import { getBreakpointsUpCached } from '../../utils/config' import { isUndefinedOrNull } from '../../utils/inspect' import { assign, create, keys } from '../../utils/object' +import { suffixPropName } from '../../utils/props' import { lowerCase } from '../../utils/string' const RX_COL_CLASS = /^col-/ diff --git a/src/components/layout/row.js b/src/components/layout/row.js index f1e027627b7..3c549e2d45c 100644 --- a/src/components/layout/row.js +++ b/src/components/layout/row.js @@ -1,10 +1,10 @@ import { mergeData } from 'vue-functional-data-merge' import identity from '../../utils/identity' import memoize from '../../utils/memoize' -import suffixPropName from '../../utils/suffix-prop-name' import { arrayIncludes, concat } from '../../utils/array' import { getBreakpointsUpCached } from '../../utils/config' import { create, keys } from '../../utils/object' +import { suffixPropName } from '../../utils/props' import { lowerCase, toString, trim } from '../../utils/string' const COMMON_ALIGNMENT = ['start', 'end', 'center'] diff --git a/src/components/link/link.js b/src/components/link/link.js index 60e5c58036a..c695258bad1 100644 --- a/src/components/link/link.js +++ b/src/components/link/link.js @@ -1,9 +1,9 @@ import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { concat } from '../../utils/array' import { getComponentConfig } from '../../utils/config' import { attemptBlur, attemptFocus } from '../../utils/dom' import { isBoolean, isEvent, isFunction, isUndefined } from '../../utils/inspect' +import { pluckProps } from '../../utils/props' import { computeHref, computeRel, computeTag, isRouterLink } from '../../utils/router' import attrsMixin from '../../mixins/attrs' import listenersMixin from '../../mixins/listeners' diff --git a/src/components/list-group/list-group-item.js b/src/components/list-group/list-group-item.js index dcfdb6fcae5..c6ed6ce9537 100644 --- a/src/components/list-group/list-group-item.js +++ b/src/components/list-group/list-group-item.js @@ -1,10 +1,10 @@ import { mergeData } from 'vue-functional-data-merge' import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { arrayIncludes } from '../../utils/array' import { getComponentConfig } from '../../utils/config' import { isTag } from '../../utils/dom' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { isLink } from '../../utils/router' import { BLink, props as BLinkProps } from '../link/link' diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js index 36ce83a9e2b..f233d937154 100644 --- a/src/components/modal/modal.js +++ b/src/components/modal/modal.js @@ -16,7 +16,7 @@ import { } from '../../utils/dom' import { isBrowser } from '../../utils/env' import { EVENT_OPTIONS_NO_CAPTURE, eventOn, eventOff } from '../../utils/events' -import { stripTags } from '../../utils/html' +import { htmlOrText } from '../../utils/html' import { isString, isUndefinedOrNull } from '../../utils/inspect' import { HTMLElement } from '../../utils/safe-types' import { BTransporterSingle } from '../../utils/transporter' @@ -863,190 +863,191 @@ export const BModal = /*#__PURE__*/ Vue.extend({ }, makeModal(h) { // Modal header - let header = h() + let $header = h() if (!this.hideHeader) { // TODO: Rename slot to `header` and deprecate `modal-header` - let modalHeader = this.normalizeSlot('modal-header', this.slotScope) - if (!modalHeader) { - let closeButton = h() + let $modalHeader = this.normalizeSlot('modal-header', this.slotScope) + if (!$modalHeader) { + let $closeButton = h() if (!this.hideHeaderClose) { - closeButton = h( + $closeButton = h( BButtonClose, { - ref: 'close-button', props: { content: this.headerCloseContent, disabled: this.isTransitioning, ariaLabel: this.headerCloseLabel, textVariant: this.headerCloseVariant || this.headerTextVariant }, - on: { click: this.onClose } + on: { click: this.onClose }, + ref: 'close-button' }, // TODO: Rename slot to `header-close` and deprecate `modal-header-close` [this.normalizeSlot('modal-header-close')] ) } - const domProps = - // TODO: Rename slot to `title` and deprecate `modal-title` - !this.hasNormalizedSlot('modal-title') && this.titleHtml - ? { innerHTML: this.titleHtml } - : {} - modalHeader = [ + + $modalHeader = [ h( this.titleTag, { staticClass: 'modal-title', class: this.titleClasses, attrs: { id: this.modalTitleId }, - domProps + // TODO: Rename slot to `title` and deprecate `modal-title` + domProps: this.hasNormalizedSlot('modal-title') + ? {} + : htmlOrText(this.titleHtml, this.title) }, // TODO: Rename slot to `title` and deprecate `modal-title` - [this.normalizeSlot('modal-title', this.slotScope) || stripTags(this.title)] + [this.normalizeSlot('modal-title', this.slotScope)] ), - closeButton + $closeButton ] } - header = h( + + $header = h( 'header', { - ref: 'header', staticClass: 'modal-header', class: this.headerClasses, - attrs: { id: this.modalHeaderId } + attrs: { id: this.modalHeaderId }, + ref: 'header' }, - [modalHeader] + [$modalHeader] ) } // Modal body - const body = h( + const $body = h( 'div', { - ref: 'body', staticClass: 'modal-body', class: this.bodyClasses, - attrs: { id: this.modalBodyId } + attrs: { id: this.modalBodyId }, + ref: 'body' }, this.normalizeSlot('default', this.slotScope) ) // Modal footer - let footer = h() + let $footer = h() if (!this.hideFooter) { // TODO: Rename slot to `footer` and deprecate `modal-footer` - let modalFooter = this.normalizeSlot('modal-footer', this.slotScope) - if (!modalFooter) { - let cancelButton = h() + let $modalFooter = this.normalizeSlot('modal-footer', this.slotScope) + if (!$modalFooter) { + let $cancelButton = h() if (!this.okOnly) { - const cancelHtml = this.cancelTitleHtml ? { innerHTML: this.cancelTitleHtml } : null - cancelButton = h( + $cancelButton = h( BButton, { - ref: 'cancel-button', props: { variant: this.cancelVariant, size: this.buttonSize, disabled: this.cancelDisabled || this.busy || this.isTransitioning }, - on: { click: this.onCancel } - }, - [ // TODO: Rename slot to `cancel-button` and deprecate `modal-cancel` - this.normalizeSlot('modal-cancel') || - (cancelHtml ? h('span', { domProps: cancelHtml }) : stripTags(this.cancelTitle)) - ] + domProps: this.hasNormalizedSlot('modal-cancel') + ? {} + : htmlOrText(this.cancelTitleHtml, this.cancelTitle), + on: { click: this.onCancel }, + ref: 'cancel-button' + }, + // TODO: Rename slot to `cancel-button` and deprecate `modal-cancel` + this.normalizeSlot('modal-cancel') ) } - const okHtml = this.okTitleHtml ? { innerHTML: this.okTitleHtml } : null - const okButton = h( + + const $okButton = h( BButton, { - ref: 'ok-button', props: { variant: this.okVariant, size: this.buttonSize, disabled: this.okDisabled || this.busy || this.isTransitioning }, - on: { click: this.onOk } - }, - [ // TODO: Rename slot to `ok-button` and deprecate `modal-ok` - this.normalizeSlot('modal-ok') || - (okHtml ? h('span', { domProps: okHtml }) : stripTags(this.okTitle)) - ] + domProps: this.hasNormalizedSlot('modal-ok') + ? {} + : htmlOrText(this.okTitleHtml, this.okTitle), + on: { click: this.onOk }, + ref: 'ok-button' + }, + // TODO: Rename slot to `ok-button` and deprecate `modal-ok` + this.normalizeSlot('modal-ok') ) - modalFooter = [cancelButton, okButton] + + $modalFooter = [$cancelButton, $okButton] } - footer = h( + + $footer = h( 'footer', { - ref: 'footer', staticClass: 'modal-footer', class: this.footerClasses, - attrs: { id: this.modalFooterId } + attrs: { id: this.modalFooterId }, + ref: 'footer' }, - [modalFooter] + [$modalFooter] ) } // Assemble modal content - const modalContent = h( + const $modalContent = h( 'div', { - ref: 'content', staticClass: 'modal-content', class: this.contentClass, attrs: { id: this.modalContentId, tabindex: '-1' - } + }, + ref: 'content' }, - [header, body, footer] + [$header, $body, $footer] ) - // Tab trap to prevent page from scrolling to next element in - // tab index during enforce focus tab cycle - let tabTrapTop = h() - let tabTrapBottom = h() + // Tab traps to prevent page from scrolling to next element in + // tab index during enforce-focus tab cycle + let $tabTrapTop = h() + let $tabTrapBottom = h() if (this.isVisible && !this.noEnforceFocus) { - tabTrapTop = h('span', { ref: 'topTrap', attrs: { tabindex: '0' } }) - tabTrapBottom = h('span', { ref: 'bottomTrap', attrs: { tabindex: '0' } }) + $tabTrapTop = h('span', { ref: 'topTrap', attrs: { tabindex: '0' } }) + $tabTrapBottom = h('span', { ref: 'bottomTrap', attrs: { tabindex: '0' } }) } // Modal dialog wrapper - const modalDialog = h( + const $modalDialog = h( 'div', { - ref: 'dialog', staticClass: 'modal-dialog', class: this.dialogClasses, - on: { mousedown: this.onDialogMousedown } + on: { mousedown: this.onDialogMousedown }, + ref: 'dialog' }, - [tabTrapTop, modalContent, tabTrapBottom] + [$tabTrapTop, $modalContent, $tabTrapBottom] ) // Modal - let modal = h( + let $modal = h( 'div', { - ref: 'modal', staticClass: 'modal', class: this.modalClasses, style: this.modalStyles, - directives: [ - { name: 'show', rawName: 'v-show', value: this.isVisible, expression: 'isVisible' } - ], attrs: this.computedModalAttrs, - on: { keydown: this.onEsc, click: this.onClickOut } + on: { keydown: this.onEsc, click: this.onClickOut }, + directives: [{ name: 'show', value: this.isVisible }], + ref: 'modal' }, - [modalDialog] + [$modalDialog] ) // Wrap modal in transition - // Sadly, we can't use BVTransition here due to the differences in - // transition durations for .modal and .modal-dialog. Not until - // issue https://github.com/vuejs/vue/issues/9986 is resolved - modal = h( + // Sadly, we can't use `BVTransition` here due to the differences in + // transition durations for `.modal` and `.modal-dialog` + // At least until https://github.com/vuejs/vue/issues/9986 is resolved + $modal = h( 'transition', { props: { @@ -1066,30 +1067,33 @@ export const BModal = /*#__PURE__*/ Vue.extend({ afterLeave: this.onAfterLeave } }, - [modal] + [$modal] ) // Modal backdrop - let backdrop = h() + let $backdrop = h() if (!this.hideBackdrop && this.isVisible) { - backdrop = h( + $backdrop = h( 'div', - { staticClass: 'modal-backdrop', attrs: { id: this.modalBackdropId } }, + { + staticClass: 'modal-backdrop', + attrs: { id: this.modalBackdropId } + }, // TODO: Rename slot to `backdrop` and deprecate `modal-backdrop` - [this.normalizeSlot('modal-backdrop')] + this.normalizeSlot('modal-backdrop') ) } - backdrop = h(BVTransition, { props: { noFade: this.noFade } }, [backdrop]) + $backdrop = h(BVTransition, { props: { noFade: this.noFade } }, [$backdrop]) // Assemble modal and backdrop in an outer
return h( 'div', { - key: `modal-outer-${this._uid}`, style: this.modalOuterStyle, - attrs: this.computedAttrs + attrs: this.computedAttrs, + key: `modal-outer-${this._uid}` }, - [modal, backdrop] + [$modal, $backdrop] ) } }, diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js index 1a3019d7cee..153cfe15cc0 100644 --- a/src/components/modal/modal.spec.js +++ b/src/components/modal/modal.spec.js @@ -335,14 +335,47 @@ describe('modal', () => { expect($cancel.attributes('type')).toBe('button') expect($cancel.text()).toContain('cancel') // `v-html` is applied to a span - expect($cancel.html()).toContain('cancel') + expect($cancel.html()).toContain('cancel') // OK button (right-most button) const $ok = $buttons.at(1) expect($ok.attributes('type')).toBe('button') expect($ok.text()).toContain('ok') // `v-html` is applied to a span - expect($ok.html()).toContain('ok') + expect($ok.html()).toContain('ok') + + wrapper.destroy() + }) + + it('modal-ok and modal-cancel button content slots works', async () => { + const wrapper = mount(BModal, { + attachTo: createContainer(), + propsData: { + static: true + }, + slots: { + 'modal-ok': 'bar ok', + 'modal-cancel': 'foo cancel' + } + }) + expect(wrapper).toBeDefined() + + const $buttons = wrapper.findAll('footer button') + expect($buttons.length).toBe(2) + + // Cancel button (left-most button) + const $cancel = $buttons.at(0) + expect($cancel.attributes('type')).toBe('button') + expect($cancel.text()).toContain('foo cancel') + // `v-html` is applied to a span + expect($cancel.html()).toContain('foo cancel') + + // OK button (right-most button) + const $ok = $buttons.at(1) + expect($ok.attributes('type')).toBe('button') + expect($ok.text()).toContain('bar ok') + // `v-html` is applied to a span + expect($ok.html()).toContain('bar ok') wrapper.destroy() }) diff --git a/src/components/nav/nav-item-dropdown.js b/src/components/nav/nav-item-dropdown.js index 199d552951b..3facb61e9d2 100644 --- a/src/components/nav/nav-item-dropdown.js +++ b/src/components/nav/nav-item-dropdown.js @@ -1,6 +1,6 @@ import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { htmlOrText } from '../../utils/html' +import { pluckProps } from '../../utils/props' import dropdownMixin from '../../mixins/dropdown' import idMixin from '../../mixins/id' import normalizeSlotMixin from '../../mixins/normalize-slot' diff --git a/src/components/navbar/navbar-brand.js b/src/components/navbar/navbar-brand.js index 835b8801192..f4bb1c71a1a 100644 --- a/src/components/navbar/navbar-brand.js +++ b/src/components/navbar/navbar-brand.js @@ -1,7 +1,7 @@ import { mergeData } from 'vue-functional-data-merge' import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { BLink, props as BLinkProps } from '../link/link' // --- Props --- diff --git a/src/components/navbar/navbar-nav.js b/src/components/navbar/navbar-nav.js index ad325d74b52..7a9e4fe89db 100644 --- a/src/components/navbar/navbar-nav.js +++ b/src/components/navbar/navbar-nav.js @@ -1,6 +1,6 @@ -import Vue from '../../utils/vue' import { mergeData } from 'vue-functional-data-merge' -import pluckProps from '../../utils/pluck-props' +import Vue from '../../utils/vue' +import { pluckProps } from '../../utils/props' import { props as BNavProps } from '../nav/nav' // -- Constants -- diff --git a/src/components/pagination-nav/pagination-nav.js b/src/components/pagination-nav/pagination-nav.js index fbcccf45c87..880831426c9 100644 --- a/src/components/pagination-nav/pagination-nav.js +++ b/src/components/pagination-nav/pagination-nav.js @@ -1,6 +1,5 @@ import Vue from '../../utils/vue' import looseEqual from '../../utils/loose-equal' -import pluckProps from '../../utils/pluck-props' import { getComponentConfig } from '../../utils/config' import { attemptBlur, requestAF } from '../../utils/dom' import { isBrowser } from '../../utils/env' @@ -8,6 +7,7 @@ import { isArray, isUndefined, isFunction, isObject } from '../../utils/inspect' import { mathMax } from '../../utils/math' import { toInteger } from '../../utils/number' import { omit } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { computeHref, parseQuery } from '../../utils/router' import { toString } from '../../utils/string' import { warn } from '../../utils/warn' diff --git a/src/components/progress/progress-bar.js b/src/components/progress/progress-bar.js index 8d853b5d585..c49cad1ce10 100644 --- a/src/components/progress/progress-bar.js +++ b/src/components/progress/progress-bar.js @@ -7,8 +7,11 @@ import { toFixed, toFloat, toInteger } from '../../utils/number' import { toString } from '../../utils/string' import normalizeSlotMixin from '../../mixins/normalize-slot' +// --- Constants --- + const NAME = 'BProgressBar' +// --- Main component --- // @vue/component export const BProgressBar = /*#__PURE__*/ Vue.extend({ name: NAME, @@ -119,16 +122,20 @@ export const BProgressBar = /*#__PURE__*/ Vue.extend({ } }, render(h) { - let childNodes = h() + const { label, labelHtml, computedValue, computedPrecision } = this + + let $content = h() + let domProps = {} if (this.hasNormalizedSlot('default')) { - childNodes = this.normalizeSlot('default') - } else if (this.label || this.labelHtml) { - childNodes = h('span', { domProps: htmlOrText(this.labelHtml, this.label) }) + $content = this.normalizeSlot('default') + } else if (label || labelHtml) { + domProps = htmlOrText(labelHtml, label) } else if (this.computedShowProgress) { - childNodes = this.computedProgress + $content = this.computedProgress } else if (this.computedShowValue) { - childNodes = toFixed(this.computedValue, this.computedPrecision) + $content = toFixed(computedValue, computedPrecision) } + return h( 'div', { @@ -139,10 +146,11 @@ export const BProgressBar = /*#__PURE__*/ Vue.extend({ role: 'progressbar', 'aria-valuemin': '0', 'aria-valuemax': toString(this.computedMax), - 'aria-valuenow': toFixed(this.computedValue, this.computedPrecision) - } + 'aria-valuenow': toFixed(computedValue, computedPrecision) + }, + domProps }, - [childNodes] + [$content] ) } }) diff --git a/src/components/table/helpers/mixin-caption.js b/src/components/table/helpers/mixin-caption.js index 33e98e49c28..4c02b4f63fa 100644 --- a/src/components/table/helpers/mixin-caption.js +++ b/src/components/table/helpers/mixin-caption.js @@ -2,7 +2,7 @@ import { htmlOrText } from '../../../utils/html' export default { props: { - // `caption-top` is part of table-redere mixin (styling) + // `caption-top` is part of table-render mixin (styling) // captionTop: { // type: Boolean, // default: false @@ -24,21 +24,21 @@ export default { }, methods: { renderCaption() { + const { caption, captionHtml } = this const h = this.$createElement - // Build the caption - const $captionSlot = this.normalizeSlot('table-caption') let $caption = h() - - if ($captionSlot || this.caption || this.captionHtml) { - const data = { - key: 'caption', - attrs: { id: this.captionId } - } - if (!$captionSlot) { - data.domProps = htmlOrText(this.captionHtml, this.caption) - } - $caption = h('caption', data, [$captionSlot]) + const hasCaptionSlot = this.hasNormalizedSlot('table-caption') + if (hasCaptionSlot || caption || captionHtml) { + $caption = h( + 'caption', + { + key: 'caption', + attrs: { id: this.captionId }, + domProps: hasCaptionSlot ? {} : htmlOrText(captionHtml, caption) + }, + this.normalizeSlot('table-caption') + ) } return $caption diff --git a/src/components/table/helpers/mixin-empty.js b/src/components/table/helpers/mixin-empty.js index a1d984838a5..1022b16b7ea 100644 --- a/src/components/table/helpers/mixin-empty.js +++ b/src/components/table/helpers/mixin-empty.js @@ -28,52 +28,66 @@ export default { renderEmpty() { const h = this.$createElement const items = this.computedItems - let $empty + let $empty = h() if ( this.showEmpty && (!items || items.length === 0) && !(this.computedBusy && this.hasNormalizedSlot('table-busy')) ) { + const { + isFiltered, + emptyText, + emptyHtml, + emptyFilteredText, + emptyFilteredHtml, + computedFields, + tbodyTrClass, + tbodyTrAttr + } = this + $empty = this.normalizeSlot(this.isFiltered ? 'emptyfiltered' : 'empty', { - emptyFilteredHtml: this.emptyFilteredHtml, - emptyFilteredText: this.emptyFilteredText, - emptyHtml: this.emptyHtml, - emptyText: this.emptyText, - fields: this.computedFields, + emptyFilteredHtml, + emptyFilteredText, + emptyHtml, + emptyText, + fields: computedFields, // Not sure why this is included, as it will always be an empty array items: this.computedItems }) + if (!$empty) { $empty = h('div', { class: ['text-center', 'my-2'], - domProps: this.isFiltered - ? htmlOrText(this.emptyFilteredHtml, this.emptyFilteredText) - : htmlOrText(this.emptyHtml, this.emptyText) + domProps: isFiltered + ? htmlOrText(emptyFilteredHtml, emptyFilteredText) + : htmlOrText(emptyHtml, emptyText) }) } - $empty = h(BTd, { props: { colspan: this.computedFields.length || null } }, [ + + $empty = h(BTd, { props: { colspan: computedFields.length || null } }, [ h('div', { attrs: { role: 'alert', 'aria-live': 'polite' } }, [$empty]) ]) + $empty = h( BTr, { - key: this.isFiltered ? 'b-empty-filtered-row' : 'b-empty-row', staticClass: 'b-table-empty-row', class: [ - isFunction(this.tbodyTrClass) + isFunction(tbodyTrClass) ? /* istanbul ignore next */ this.tbodyTrClass(null, 'row-empty') - : this.tbodyTrClass + : tbodyTrClass ], - attrs: isFunction(this.tbodyTrAttr) + attrs: isFunction(tbodyTrAttr) ? /* istanbul ignore next */ this.tbodyTrAttr(null, 'row-empty') - : this.tbodyTrAttr + : tbodyTrAttr, + key: isFiltered ? 'b-empty-filtered-row' : 'b-empty-row' }, [$empty] ) } - return $empty || h() + return $empty } } } diff --git a/src/components/table/helpers/mixin-thead.js b/src/components/table/helpers/mixin-thead.js index 17848abb801..527f3f97271 100644 --- a/src/components/table/helpers/mixin-thead.js +++ b/src/components/table/helpers/mixin-thead.js @@ -1,5 +1,6 @@ import identity from '../../../utils/identity' import KeyCodes from '../../../utils/key-codes' +import noop from '../../../utils/noop' import startCase from '../../../utils/startcase' import { getComponentConfig } from '../../../utils/config' import { htmlOrText } from '../../../utils/html' @@ -56,18 +57,30 @@ export default { const h = this.$createElement const fields = this.computedFields || [] + // In always stacked mode, we don't bother rendering the head/foot + // Or if no field headings (empty table) if (this.isStackedAlways || fields.length === 0) { - // In always stacked mode, we don't bother rendering the head/foot - // Or if no field headings (empty table) return h() } + const { + isSortable, + isSelectable, + headVariant, + footVariant, + headRowVariant, + footRowVariant + } = this + const hasHeadClickListener = isSortable || this.hasListener('head-clicked') + // Reference to `selectAllRows` and `clearSelected()`, if table is selectable - const selectAllRows = this.isSelectable ? this.selectAllRows : () => {} - const clearSelected = this.isSelectable ? this.clearSelected : () => {} + const selectAllRows = isSelectable ? this.selectAllRows : noop + const clearSelected = isSelectable ? this.clearSelected : noop // Helper function to generate a field cell const makeCell = (field, colIndex) => { + const { label, labelHtml, variant, stickyColumn, key } = field + let ariaLabel = null if (!field.label.trim() && !field.headerTitle) { // In case field's label and title are empty/blank @@ -75,29 +88,27 @@ export default { /* istanbul ignore next */ ariaLabel = startCase(field.key) } - const hasHeadClickListener = this.hasListener('head-clicked') || this.isSortable - const handlers = {} + + const on = {} if (hasHeadClickListener) { - handlers.click = evt => { + on.click = evt => { this.headClicked(evt, field, isFoot) } - handlers.keydown = evt => { + on.keydown = evt => { const keyCode = evt.keyCode if (keyCode === KeyCodes.ENTER || keyCode === KeyCodes.SPACE) { this.headClicked(evt, field, isFoot) } } } - const sortAttrs = this.isSortable ? this.sortTheadThAttrs(field.key, field, isFoot) : {} - const sortClass = this.isSortable ? this.sortTheadThClasses(field.key, field, isFoot) : null - const sortLabel = this.isSortable ? this.sortTheadThLabel(field.key, field, isFoot) : null + + const sortAttrs = isSortable ? this.sortTheadThAttrs(key, field, isFoot) : {} + const sortClass = isSortable ? this.sortTheadThClasses(key, field, isFoot) : null + const sortLabel = isSortable ? this.sortTheadThLabel(key, field, isFoot) : null + const data = { - key: field.key, class: [this.fieldClasses(field), sortClass], - props: { - variant: field.variant, - stickyColumn: field.stickyColumn - }, + props: { variant, stickyColumn }, style: field.thStyle || {}, attrs: { // We only add a tabindex of 0 if there is a head-clicked listener @@ -106,41 +117,42 @@ export default { title: field.headerTitle || null, 'aria-colindex': colIndex + 1, 'aria-label': ariaLabel, - ...this.getThValues(null, field.key, field.thAttr, isFoot ? 'foot' : 'head', {}), + ...this.getThValues(null, key, field.thAttr, isFoot ? 'foot' : 'head', {}), ...sortAttrs }, - on: handlers + on, + key } + // Handle edge case where in-document templates are used with new // `v-slot:name` syntax where the browser lower-cases the v-slot's // name (attributes become lower cased when parsed by the browser) // We have replaced the square bracket syntax with round brackets // to prevent confusion with dynamic slot names - let slotNames = [`head(${field.key})`, `head(${field.key.toLowerCase()})`, 'head()'] + let slotNames = [`head(${key})`, `head(${key.toLowerCase()})`, 'head()'] + // Footer will fallback to header slot names if (isFoot) { - // Footer will fallback to header slot names - slotNames = [ - `foot(${field.key})`, - `foot(${field.key.toLowerCase()})`, - 'foot()', - ...slotNames - ] + slotNames = [`foot(${key})`, `foot(${key.toLowerCase()})`, 'foot()', ...slotNames] } + const scope = { - label: field.label, - column: field.key, + label, + column: key, field, isFoot, // Add in row select methods selectAllRows, clearSelected } - const content = + + const $content = this.normalizeSlot(slotNames, scope) || - (field.labelHtml ? h('div', { domProps: htmlOrText(field.labelHtml) }) : field.label) - const srLabel = sortLabel ? h('span', { staticClass: 'sr-only' }, ` (${sortLabel})`) : null + h('div', { domProps: htmlOrText(labelHtml, label) }) + + const $srLabel = sortLabel ? h('span', { staticClass: 'sr-only' }, ` (${sortLabel})`) : null + // Return the header cell - return h(BTh, data, [content, srLabel].filter(identity)) + return h(BTh, data, [$content, $srLabel].filter(identity)) } // Generate the array of cells @@ -149,23 +161,39 @@ export default { // Generate the row(s) const $trs = [] if (isFoot) { - const trProps = { - variant: isUndefinedOrNull(this.footRowVariant) - ? this.headRowVariant - : /* istanbul ignore next */ this.footRowVariant - } - $trs.push(h(BTr, { class: this.tfootTrClass, props: trProps }, $cells)) + $trs.push( + h( + BTr, + { + class: this.tfootTrClass, + props: { + variant: isUndefinedOrNull(footRowVariant) + ? headRowVariant + : /* istanbul ignore next */ footRowVariant + } + }, + $cells + ) + ) } else { const scope = { columns: fields.length, - fields: fields, + fields, // Add in row select methods selectAllRows, clearSelected } $trs.push(this.normalizeSlot('thead-top', scope) || h()) + $trs.push( - h(BTr, { class: this.theadTrClass, props: { variant: this.headRowVariant } }, $cells) + h( + BTr, + { + class: this.theadTrClass, + props: { variant: headRowVariant } + }, + $cells + ) ) } @@ -175,8 +203,8 @@ export default { key: isFoot ? 'bv-tfoot' : 'bv-thead', class: (isFoot ? this.tfootClass : this.theadClass) || null, props: isFoot - ? { footVariant: this.footVariant || this.headVariant || null } - : { headVariant: this.headVariant || null } + ? { footVariant: footVariant || headVariant || null } + : { headVariant: headVariant || null } }, $trs ) diff --git a/src/components/toast/toast.js b/src/components/toast/toast.js index 3f62de144e5..a028e71e870 100644 --- a/src/components/toast/toast.js +++ b/src/components/toast/toast.js @@ -1,7 +1,6 @@ import { Portal, Wormhole } from 'portal-vue' import BVTransition from '../../utils/bv-transition' import Vue from '../../utils/vue' -import pluckProps from '../../utils/pluck-props' import { BvEvent } from '../../utils/bv-event.class' import { getComponentConfig } from '../../utils/config' import { requestAF } from '../../utils/dom' @@ -9,6 +8,7 @@ import { EVENT_OPTIONS_NO_CAPTURE, eventOnOff } from '../../utils/events' import { mathMax } from '../../utils/math' import { toInteger } from '../../utils/number' import { pick } from '../../utils/object' +import { pluckProps } from '../../utils/props' import { isLink } from '../../utils/router' import attrsMixin from '../../mixins/attrs' import idMixin from '../../mixins/id' diff --git a/src/mixins/form-radio-check-group.js b/src/mixins/form-radio-check-group.js index 4d79d68e171..3c7991bebee 100644 --- a/src/mixins/form-radio-check-group.js +++ b/src/mixins/form-radio-check-group.js @@ -75,14 +75,14 @@ export default { } }, render(h) { - const inputs = this.formOptions.map((option, idx) => { - const uid = `_BV_option_${idx}_` + const $inputs = this.formOptions.map((option, index) => { + const key = `BV_option_${index}` + return h( this.isRadioGroup ? BFormRadio : BFormCheckbox, { - key: uid, props: { - id: this.safeId(uid), + id: this.safeId(key), value: option.value, // Individual radios or checks can be disabled in a group disabled: option.disabled || false @@ -90,11 +90,13 @@ export default { // name: this.groupName, // form: this.form || null, // required: Boolean(this.name && this.required) - } + }, + key }, [h('span', { domProps: htmlOrText(option.html, option.text) })] ) }) + return h( 'div', { @@ -102,14 +104,13 @@ export default { attrs: { id: this.safeId(), role: this.isRadioGroup ? 'radiogroup' : 'group', - // Tabindex to allow group to be focused - // if needed by screen readers + // Add `tabindex="-1"` to allow group to be focused if needed by screen readers tabindex: '-1', 'aria-required': this.required ? 'true' : null, 'aria-invalid': this.computedAriaInvalid } }, - [this.normalizeSlot('first'), inputs, this.normalizeSlot('default')] + [this.normalizeSlot('first'), $inputs, this.normalizeSlot('default')] ) } } diff --git a/src/utils/copy-props.js b/src/utils/copy-props.js deleted file mode 100644 index 31768e7e550..00000000000 --- a/src/utils/copy-props.js +++ /dev/null @@ -1,33 +0,0 @@ -import identity from './identity' -import { isArray, isObject } from './inspect' -import { clone } from './object' - -/** - * Copies props from one array/object to a new array/object. Prop values - * are also cloned as new references to prevent possible mutation of original - * prop object values. Optionally accepts a function to transform the prop name. - * - * @param {[]|{}} props - * @param {Function} transformFn - */ -const copyProps = (props, transformFn = identity) => { - if (isArray(props)) { - return props.map(transformFn) - } - // Props as an object. - const copied = {} - - for (const prop in props) { - /* istanbul ignore else */ - // eslint-disable-next-line no-prototype-builtins - if (props.hasOwnProperty(prop)) { - // If the prop value is an object, do a shallow clone to prevent - // potential mutations to the original object. - copied[transformFn(prop)] = isObject(props[prop]) ? clone(props[prop]) : props[prop] - } - } - - return copied -} - -export default copyProps diff --git a/src/utils/html.js b/src/utils/html.js index ea5d2181659..4bd325a0a59 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -1,9 +1,9 @@ -const stripTagsRegex = /(<([^>]+)>)/gi +const RX_HTML_TAGS = /(<([^>]+)>)/gi -// Removes any thing that looks like an HTML tag from the supplied string -export const stripTags = (text = '') => String(text).replace(stripTagsRegex, '') +// Removes anything that looks like an HTML tag from the supplied string +export const stripTags = (text = '') => String(text).replace(RX_HTML_TAGS, '') -// Generate a domProps object for either innerHTML, textContent or nothing +// Generate a `domProps` object for either `innerHTML`, `textContent` or an empty object export const htmlOrText = (innerHTML, textContent) => { return innerHTML ? { innerHTML } : textContent ? { textContent } : {} } diff --git a/src/utils/pluck-props.js b/src/utils/pluck-props.js deleted file mode 100644 index f20260397fb..00000000000 --- a/src/utils/pluck-props.js +++ /dev/null @@ -1,21 +0,0 @@ -import identity from './identity' -import { isArray } from './inspect' -import { keys } from './object' - -/** - * Given an array of properties or an object of property keys, - * plucks all the values off the target object, returning a new object - * that has props that reference the original prop values - * - * @param {{}|string[]} keysToPluck - * @param {{}} objToPluck - * @param {Function} transformFn - * @return {{}} - */ -const pluckProps = (keysToPluck, objToPluck, transformFn = identity) => - (isArray(keysToPluck) ? keysToPluck.slice() : keys(keysToPluck)).reduce((memo, prop) => { - memo[transformFn(prop)] = objToPluck[prop] - return memo - }, {}) - -export default pluckProps diff --git a/src/utils/prefix-prop-name.js b/src/utils/prefix-prop-name.js deleted file mode 100644 index 3dcb1c5e2b6..00000000000 --- a/src/utils/prefix-prop-name.js +++ /dev/null @@ -1,9 +0,0 @@ -import { upperFirst } from './string' - -/** - * @param {string} prefix - * @param {string} value - */ -const prefixPropName = (prefix, value) => prefix + upperFirst(value) - -export default prefixPropName diff --git a/src/utils/props.js b/src/utils/props.js new file mode 100644 index 00000000000..09bd3ee05c5 --- /dev/null +++ b/src/utils/props.js @@ -0,0 +1,44 @@ +import identity from './identity' +import { isArray, isObject } from './inspect' +import { clone, hasOwnProperty, keys } from './object' +import { lowerFirst, upperFirst } from './string' + +// Prefix a property +export const prefixPropName = (prefix, value) => prefix + upperFirst(value) + +// Remove a prefix from a property +export const unprefixPropName = (prefix, value) => lowerFirst(value.replace(prefix, '')) + +// Suffix can be a falsey value so nothing is appended to string +// (helps when looping over props & some shouldn't change) +// Use data last parameters to allow for currying +export const suffixPropName = (suffix, str) => str + (suffix ? upperFirst(suffix) : '') + +// Copies props from one array/object to a new array/object +// Prop values are also cloned as new references to prevent possible +// mutation of original prop object values +// Optionally accepts a function to transform the prop name +export const copyProps = (props, transformFn = identity) => { + if (isArray(props)) { + return props.map(transformFn) + } + const copied = {} + for (const prop in props) { + /* istanbul ignore else */ + if (hasOwnProperty(props, prop)) { + // If the prop value is an object, do a shallow clone + // to prevent potential mutations to the original object + copied[transformFn(prop)] = isObject(props[prop]) ? clone(props[prop]) : props[prop] + } + } + return copied +} + +// Given an array of properties or an object of property keys, +// plucks all the values off the target object, returning a new object +// that has props that reference the original prop values +export const pluckProps = (keysToPluck, objToPluck, transformFn = identity) => + (isArray(keysToPluck) ? keysToPluck.slice() : keys(keysToPluck)).reduce((memo, prop) => { + memo[transformFn(prop)] = objToPluck[prop] + return memo + }, {}) diff --git a/src/utils/copy-props.spec.js b/src/utils/props.spec.js similarity index 81% rename from src/utils/copy-props.spec.js rename to src/utils/props.spec.js index a9695f5433c..30d13ff6bcd 100644 --- a/src/utils/copy-props.spec.js +++ b/src/utils/props.spec.js @@ -1,7 +1,7 @@ -import copyProps from './copy-props' +import { copyProps } from './props' -describe('utils/copyProps', () => { - it('works with array props', async () => { +describe('utils/props', () => { + it('copyProps() works with array props', async () => { const props = ['a', 'b', 'c'] expect(copyProps(props)).toEqual(props) @@ -9,7 +9,7 @@ describe('utils/copyProps', () => { expect(copyProps(props)).not.toBe(props) }) - it('works with object props', async () => { + it('copyProps() works with object props', async () => { const props = { a: { type: String, default: 'foobar' }, b: { type: [Object, Array], default: null }, diff --git a/src/utils/suffix-prop-name.js b/src/utils/suffix-prop-name.js deleted file mode 100644 index 0bd1ec96b43..00000000000 --- a/src/utils/suffix-prop-name.js +++ /dev/null @@ -1,12 +0,0 @@ -import { upperFirst } from './string' - -/** - * Suffix can be a falsey value so nothing is appended to string. - * (helps when looping over props & some shouldn't change) - * Use data last parameters to allow for currying. - * @param {string} suffix - * @param {string} str - */ -const suffixPropName = (suffix, str) => str + (suffix ? upperFirst(suffix) : '') - -export default suffixPropName diff --git a/src/utils/unprefix-prop-name.js b/src/utils/unprefix-prop-name.js deleted file mode 100644 index df4b15b9f68..00000000000 --- a/src/utils/unprefix-prop-name.js +++ /dev/null @@ -1,9 +0,0 @@ -import { lowerFirst } from './string' - -/** - * @param {string} prefix - * @param {string} value - */ -const unprefixPropName = (prefix, value) => lowerFirst(value.replace(prefix, '')) - -export default unprefixPropName From 0a679c8fc875bc011a1e8890376ef98e6b799c54 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 May 2020 13:25:03 +0200 Subject: [PATCH 13/41] chore(deps): update devdependency rollup to ^2.10.2 (#5379) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e778d631e12..2e17b1a7428 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "postcss-cli": "^7.1.1", "prettier": "1.14.3", "require-context": "^1.1.0", - "rollup": "^2.10.1", + "rollup": "^2.10.2", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index a2eb3c9eb06..587f01e4c4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11296,10 +11296,10 @@ rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^2.10.1: - version "2.10.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.1.tgz#18a97703f0cb6e3a1711a3e4db42021d5290cecc" - integrity sha512-d7e9QM49YAnFjqxTm4HbzDMLX6KeTKEd7UY7uCqP/uVCviXEjyl4e/sGfulTIgvZHVYFS4jXhr+Qugp+WPHkYQ== +rollup@^2.10.2: + version "2.10.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.2.tgz#9adfcf8ab36861b5b0f8ca7b436f5866e3e9e200" + integrity sha512-tivFM8UXBlYOUqpBYD3pRktYpZvK/eiCQ190eYlrAyrpE/lzkyG2gbawroNdbwmzyUc7Y4eT297xfzv0BDh9qw== optionalDependencies: fsevents "~2.1.2" From a948846400c37fca0fa3ed673b1c4684fc6f69e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20M=C3=BCller?= Date: Fri, 15 May 2020 18:43:28 +0200 Subject: [PATCH 14/41] fix: CodeSandbox integration (#5381) --- .codesandbox/ci.json | 2 +- docs/pages/play.vue | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index a2185b8a558..5bc7e29a166 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,3 +1,3 @@ { - "sandboxes": ["0d335"] + "sandboxes": ["qeu9j"] } diff --git a/docs/pages/play.vue b/docs/pages/play.vue index ecbe7a1036f..b2b43a41ef9 100644 --- a/docs/pages/play.vue +++ b/docs/pages/play.vue @@ -491,17 +491,24 @@ export default { '', "new Vue({ el: '#app', render: h => h(App) })" ].join('\r\n') + const scripts = { + serve: 'vue-cli-service serve', + build: 'vue-cli-service build' + } const dependencies = { bootstrap: bootstrapVersion, 'bootstrap-vue': bootstrapVueVersion, vue: vueVersion } + const devDependencies = { + '@vue/cli-service': '^4.3.0' + } return getCodeSandboxParameters({ files: { 'App.vue': { content: vueContent }, 'index.html': { content: htmlContent }, 'index.js': { content: jsContent }, - 'package.json': { content: { dependencies } } + 'package.json': { content: { scripts, dependencies, devDependencies } } } }) }, From 843318243c6061afa13ebf8fd1174eab5c150d35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 May 2020 12:03:38 +0200 Subject: [PATCH 15/41] chore(deps): update devdependency eslint-plugin-jest to ^23.12.0 (#5383) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2e17b1a7428..95465e061c6 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "eslint-config-standard": "^14.1.1", "eslint-config-vue": "^2.0.2", "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^23.11.0", + "eslint-plugin-jest": "^23.12.0", "eslint-plugin-markdown": "^1.0.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index 587f01e4c4f..dfa2be9e960 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5129,10 +5129,10 @@ eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-jest@^23.11.0: - version "23.11.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.11.0.tgz#6e01d83ea74c1eefd60811655bbc288bd8ab2e7d" - integrity sha512-qedvh6mcMgoLFHjITtG40yKOCu5Fa1GMYesDOclU30ZvtVkf+DaH0fnCn1ysOX/QMdk2SGhQvxvYLowcLaM0GA== +eslint-plugin-jest@^23.12.0: + version "23.12.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.12.0.tgz#12eed51f43dc478a46c49093973878e863f4b585" + integrity sha512-lHA8D3TfDqhO5sPEEp3k87Ljo6Ci09lMmWrNhrAReWs04VBgTar8UAKKFKpA+zTKHG7s5IqKRwZlrdEeq6JE2A== dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" From e9d426dbce4a8409104c3edca21b7e5759780e7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 May 2020 16:57:05 +0200 Subject: [PATCH 16/41] chore(deps): update devdependency eslint-plugin-jest to ^23.13.0 (#5384) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 95465e061c6..1979fe0b9e5 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "eslint-config-standard": "^14.1.1", "eslint-config-vue": "^2.0.2", "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^23.12.0", + "eslint-plugin-jest": "^23.13.0", "eslint-plugin-markdown": "^1.0.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index dfa2be9e960..ac996359c5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5129,10 +5129,10 @@ eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-jest@^23.12.0: - version "23.12.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.12.0.tgz#12eed51f43dc478a46c49093973878e863f4b585" - integrity sha512-lHA8D3TfDqhO5sPEEp3k87Ljo6Ci09lMmWrNhrAReWs04VBgTar8UAKKFKpA+zTKHG7s5IqKRwZlrdEeq6JE2A== +eslint-plugin-jest@^23.13.0: + version "23.13.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.13.0.tgz#a80ec992b155dd15260bf1131bad666e5123e551" + integrity sha512-AG18G0qFCgFOdhMibl4cTuKTCBVjzm/hEPYMOPCTj3Yh2GQ53q5u5Jj3a4nl1JH1BxqPsXIrZR1oRE+TdptfHw== dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" From 7ac485457504fe94e16ded5f10515e8e7779d7fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 May 2020 10:37:24 +0200 Subject: [PATCH 17/41] chore(deps): update all non-major dependencies (#5386) Co-authored-by: Renovate Bot --- package.json | 6 ++--- yarn.lock | 68 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 1979fe0b9e5..1b9e7ebdc80 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "@nuxtjs/sitemap": "^2.3.0", "@testing-library/jest-dom": "^5.7.0", "@vue/test-utils": "^1.0.2", - "autoprefixer": "^9.7.6", + "autoprefixer": "^9.8.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.1.0", "babel-jest": "^26.0.1", @@ -138,12 +138,12 @@ "highlight.js": "^9.18.1", "html-loader": "^1.1.0", "husky": "^4.2.5", - "improved-yarn-audit": "^2.0.0", + "improved-yarn-audit": "^2.1.0", "jest": "^26.0.1", "lint-staged": "^10.2.2", "loader-utils": "^2.0.0", "lodash": "^4.17.15", - "marked": "^1.0.0", + "marked": "^1.1.0", "node-sass": "^4.14.1", "nuxt": "^2.12.2", "postcss-cli": "^7.1.1", diff --git a/yarn.lock b/yarn.lock index ac996359c5e..5af781655f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2499,18 +2499,18 @@ autoprefixer@^9.6.1: postcss "^7.0.27" postcss-value-parser "^4.0.3" -autoprefixer@^9.7.6: - version "9.7.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4" - integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ== +autoprefixer@^9.8.0: + version "9.8.0" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.0.tgz#68e2d2bef7ba4c3a65436f662d0a56a741e56511" + integrity sha512-D96ZiIHXbDmU02dBaemyAg53ez+6F5yZmapmgKcjm35yEe1uVDYI8hGW3VYoGRaG290ZFf91YxHrR518vC0u/A== dependencies: - browserslist "^4.11.1" - caniuse-lite "^1.0.30001039" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001061" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.27" - postcss-value-parser "^4.0.3" + postcss "^7.0.30" + postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: version "0.7.0" @@ -2990,6 +2990,16 @@ browserslist@^4.0.0, browserslist@^4.11.0, browserslist@^4.11.1, browserslist@^4 node-releases "^1.1.53" pkg-up "^2.0.0" +browserslist@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== + dependencies: + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" + node-releases "^1.1.53" + pkg-up "^2.0.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3277,11 +3287,16 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001036, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001036, caniuse-lite@^1.0.30001038: version "1.0.30001039" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== +caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: + version "1.0.30001061" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001061.tgz#80ca87ef14eb543a7458e7fd2b5e2face3458c9f" + integrity sha512-SMICCeiNvMZnyXpuoO+ot7FHpMVPlrsR+HmfByj6nY4xYDHXLqMTbgH7ecEkDNXWkH1vaip+ZS0D7VTXwM1KYQ== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -4881,6 +4896,11 @@ electron-to-chromium@^1.3.390: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.396.tgz#d47570afad5f772dd93973f51e3e334cd591a266" integrity sha512-ESY3UGekvNQwofHvgdsFW8GQEoudbqtJfoSDovnsCRRx8t0+0dPbE1XD/ZQdB+jbskSyPwUtIVYSyKwSXW/A6Q== +electron-to-chromium@^1.3.413: + version "1.3.441" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.441.tgz#094f71b992dca5bc96b798cfbaf37dc76302015a" + integrity sha512-leBfJwLuyGs1jEei2QioI+PjVMavmUIvPYidE8dCCYWLAq0uefhN3NYgDNb8WxD3uiUNnJ3ScMXg0upSlwySzQ== + elegant-spinner@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-2.0.0.tgz#f236378985ecd16da75488d166be4b688fd5af94" @@ -6778,10 +6798,10 @@ import-local@^3.0.2: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" -improved-yarn-audit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/improved-yarn-audit/-/improved-yarn-audit-2.0.0.tgz#8819a7c22c0fd9c6df4b49a4c2f88a33e31c469e" - integrity sha512-IfySlTAOEEdI/MDSgH0WO8K28K1z4KQr5eEpUJyvuF1aAfWp/PTxuonboRxd0oKWuPZcrUsMZxwKvN4jpEIJeg== +improved-yarn-audit@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/improved-yarn-audit/-/improved-yarn-audit-2.1.0.tgz#de87f6960ef53a867c5bc031f155a2cda909deff" + integrity sha512-Aus0dM+kbWm1srPkWunnQh1ddfyBRM8I43ACx+blzazBvlw4fC9IRBs3HDQwFOtJZh1gNTgMHSEH8xagpSmzSg== imurmurhash@^0.1.4: version "0.1.4" @@ -8422,10 +8442,10 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== -marked@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-1.0.0.tgz#d35784245a04871e5988a491e28867362e941693" - integrity sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng== +marked@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.0.tgz#62504ad4d11550c942935ccc5e39d64e5a4c4e50" + integrity sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA== md5.js@^1.3.4: version "1.3.5" @@ -10437,6 +10457,11 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" @@ -10455,6 +10480,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1 source-map "^0.6.1" supports-color "^6.1.0" +postcss@^7.0.30: + version "7.0.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2" + integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" From 081bdd286597c36991177452ab66a0c86c08709b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 May 2020 13:17:55 +0200 Subject: [PATCH 18/41] chore(deps): update devdependency eslint-plugin-jest to ^23.13.1 (#5387) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1b9e7ebdc80..1b1857a7e87 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "eslint-config-standard": "^14.1.1", "eslint-config-vue": "^2.0.2", "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^23.13.0", + "eslint-plugin-jest": "^23.13.1", "eslint-plugin-markdown": "^1.0.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index 5af781655f3..f7d8e11134b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5149,10 +5149,10 @@ eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" -eslint-plugin-jest@^23.13.0: - version "23.13.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.13.0.tgz#a80ec992b155dd15260bf1131bad666e5123e551" - integrity sha512-AG18G0qFCgFOdhMibl4cTuKTCBVjzm/hEPYMOPCTj3Yh2GQ53q5u5Jj3a4nl1JH1BxqPsXIrZR1oRE+TdptfHw== +eslint-plugin-jest@^23.13.1: + version "23.13.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.13.1.tgz#b2ce83f76064ad8ba1f1f26f322b86a86e44148e" + integrity sha512-TRLJH6M6EDvGocD98a7yVThrAOCK9WJfo9phuUb0MJptcrOYZeCKzC9aOzZCD93sxXCsiJVZywaTHdI/mAi0FQ== dependencies: "@typescript-eslint/experimental-utils" "^2.5.0" From 869d2f07b688d039b685741cacc73e6e6b5dc6fa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 07:36:48 +0200 Subject: [PATCH 19/41] chore(deps): update devdependency @vue/test-utils to ^1.0.3 (#5388) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1b1857a7e87..30434430097 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "@nuxtjs/robots": "^2.4.2", "@nuxtjs/sitemap": "^2.3.0", "@testing-library/jest-dom": "^5.7.0", - "@vue/test-utils": "^1.0.2", + "@vue/test-utils": "^1.0.3", "autoprefixer": "^9.8.0", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.1.0", diff --git a/yarn.lock b/yarn.lock index f7d8e11134b..25be3c57077 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1917,10 +1917,10 @@ source-map "~0.6.1" vue-template-es2015-compiler "^1.9.0" -"@vue/test-utils@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.2.tgz#68134747cb88d996e4c9703ca4b103b4d23fda14" - integrity sha512-pnRWJbb0cLqjSJIKRpqoSISeYtufEn8D16VmhlCrDWIVt4iAY4Og4JpOPmFytvtQVz96p6n7T6ERI55ue6n0Ew== +"@vue/test-utils@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.3.tgz#587c4dd9b424b66022f188c19bc605da2ce91c6f" + integrity sha512-mmsKXZSGfvd0bH05l4SNuczZ2MqlJH2DWhiul5wJXFxbf/gRRd2UL4QZgozEMQ30mRi9i4/+p4JJat8S4Js64Q== dependencies: dom-event-types "^1.0.0" lodash "^4.17.15" From 26091042104cbaad3f98ed23e3626f3425635b4b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 16:49:59 +0200 Subject: [PATCH 20/41] chore(deps): update devdependency lint-staged to ^10.2.3 (#5389) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 73 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 30434430097..4ac4ed2bb80 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "husky": "^4.2.5", "improved-yarn-audit": "^2.1.0", "jest": "^26.0.1", - "lint-staged": "^10.2.2", + "lint-staged": "^10.2.3", "loader-utils": "^2.0.0", "lodash": "^4.17.15", "marked": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index 25be3c57077..94ff5e729aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3709,6 +3709,11 @@ commander@^5.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.0.0.tgz#dbf1909b49e5044f8fdaf0adc809f0c0722bdfd0" integrity sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -4972,7 +4977,7 @@ enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: memory-fs "^0.5.0" tapable "^1.0.0" -enquirer@^2.3.4: +enquirer@^2.3.5: version "2.3.5" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== @@ -5432,6 +5437,21 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.1.tgz#988488781f1f0238cd156f7aaede11c3e853b4c1" + integrity sha512-SCjM/zlBdOK8Q5TIjOn6iEHZaPHFsMoTxXQ2nvUvtPnuohz3H2dIozSg+etNR98dGoYUp2ENSKLL/XaMmbxVgw== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -8095,44 +8115,44 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@^10.2.2: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.2.tgz#901403c120eb5d9443a0358b55038b04c8a7db9b" - integrity sha512-78kNqNdDeKrnqWsexAmkOU3Z5wi+1CsQmUmfCuYgMTE8E4rAIX8RHW7xgxwAZ+LAayb7Cca4uYX4P3LlevzjVg== +lint-staged@^10.2.3: + version "10.2.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.3.tgz#22fb3e251b915e64a46d3eead03a5e37e969eac3" + integrity sha512-dX5CeCIDtKoNv5IQpOMq/YUP8PXc58/QW+iWapv8KzgYcbgUWJwhPaFjX5aUS9pAa8cpGH7Jur3Puw3tugTGIg== dependencies: chalk "^4.0.0" - commander "^5.0.0" + commander "^5.1.0" cosmiconfig "^6.0.0" debug "^4.1.1" dedent "^0.7.0" - execa "^4.0.0" - listr2 "1.3.8" - log-symbols "^3.0.0" + execa "^4.0.1" + listr2 "2.0.1" + log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" please-upgrade-node "^3.2.0" string-argv "0.3.1" stringify-object "^3.3.0" -listr2@1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-1.3.8.tgz#30924d79de1e936d8c40af54b6465cb814a9c828" - integrity sha512-iRDRVTgSDz44tBeBBg/35TQz4W+EZBWsDUq7hPpqeUHm7yLPNll0rkwW3lIX9cPAK7l+x95mGWLpxjqxftNfZA== +listr2@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.0.1.tgz#06a05868795da04d91a8ad86ab299c166602ad80" + integrity sha512-APezMtg3gQTamAgixvFKPpq8ipQJix5dJcEw4S+UzF4k5fNn1Bm0ssULO01ekr3PkDT/hoT0bMKBb8hNFDAl2g== dependencies: "@samverschueren/stream-to-observable" "^0.3.0" - chalk "^3.0.0" + chalk "^4.0.0" cli-cursor "^3.1.0" cli-truncate "^2.1.0" elegant-spinner "^2.0.0" - enquirer "^2.3.4" + enquirer "^2.3.5" figures "^3.2.0" indent-string "^4.0.0" log-update "^4.0.0" p-map "^4.0.0" pad "^3.2.0" - rxjs "^6.3.3" + rxjs "^6.5.5" through "^2.3.8" - uuid "^7.0.2" + uuid "^8.0.0" load-json-file@^1.0.0: version "1.1.0" @@ -8293,12 +8313,12 @@ log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== dependencies: - chalk "^2.4.2" + chalk "^4.0.0" log-update@^4.0.0: version "4.0.0" @@ -11361,7 +11381,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3: +rxjs@^6.4.0, rxjs@^6.5.3, rxjs@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== @@ -13082,11 +13102,16 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.2, uuid@^7.0.3: +uuid@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" From f14e9f0ced2025ef26b8904f41ead32895b1a208 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 21:30:35 +0200 Subject: [PATCH 21/41] chore(deps): update all non-major dependencies (#5390) Co-authored-by: Renovate Bot --- package.json | 4 ++-- yarn.lock | 35 +++++++++++++++-------------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 4ac4ed2bb80..7c502715b84 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "husky": "^4.2.5", "improved-yarn-audit": "^2.1.0", "jest": "^26.0.1", - "lint-staged": "^10.2.3", + "lint-staged": "^10.2.4", "loader-utils": "^2.0.0", "lodash": "^4.17.15", "marked": "^1.1.0", @@ -155,7 +155,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "sass-loader": "^8.0.2", "standard-version": "^8.0.0", - "terser": "^4.6.13", + "terser": "^4.7.0", "vue": "^2.6.11", "vue-jest": "^3.0.5", "vue-router": "^3.1.6", diff --git a/yarn.lock b/yarn.lock index 94ff5e729aa..d156d03529c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8115,10 +8115,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lint-staged@^10.2.3: - version "10.2.3" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.3.tgz#22fb3e251b915e64a46d3eead03a5e37e969eac3" - integrity sha512-dX5CeCIDtKoNv5IQpOMq/YUP8PXc58/QW+iWapv8KzgYcbgUWJwhPaFjX5aUS9pAa8cpGH7Jur3Puw3tugTGIg== +lint-staged@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.4.tgz#0ed5d1cf06bdac0d3fbb003931bb6df3771fbf42" + integrity sha512-doTMGKXQAT34c3S3gwDrTnXmCZp/z1/92D8suPqqh755sKPT18ew1NoPNHxJdrvv1D4WrJ7CEnx79Ns3EdEFbg== dependencies: chalk "^4.0.0" commander "^5.1.0" @@ -8126,7 +8126,7 @@ lint-staged@^10.2.3: debug "^4.1.1" dedent "^0.7.0" execa "^4.0.1" - listr2 "2.0.1" + listr2 "^2.0.2" log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" @@ -8134,10 +8134,10 @@ lint-staged@^10.2.3: string-argv "0.3.1" stringify-object "^3.3.0" -listr2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.0.1.tgz#06a05868795da04d91a8ad86ab299c166602ad80" - integrity sha512-APezMtg3gQTamAgixvFKPpq8ipQJix5dJcEw4S+UzF4k5fNn1Bm0ssULO01ekr3PkDT/hoT0bMKBb8hNFDAl2g== +listr2@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.0.2.tgz#35e11e742ee151a8c446d1649792cadf7eb1d780" + integrity sha512-HkbraLsbHRFtuT0p1g9KUiMoJeqlPdgsi4Q3mCvBlYnVK+2I1vPdCxBvJ+nAxwpL7SZiyaICWMvLOyMBtu+VKw== dependencies: "@samverschueren/stream-to-observable" "^0.3.0" chalk "^4.0.0" @@ -8152,7 +8152,7 @@ listr2@2.0.1: pad "^3.2.0" rxjs "^6.5.5" through "^2.3.8" - uuid "^8.0.0" + uuid "^7.0.2" load-json-file@^1.0.0: version "1.1.0" @@ -12470,10 +12470,10 @@ terser@^4.1.2, terser@^4.4.3, terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^4.6.13: - version "4.6.13" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.13.tgz#e879a7364a5e0db52ba4891ecde007422c56a916" - integrity sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw== +terser@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006" + integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -13102,16 +13102,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.3: +uuid@^7.0.2, uuid@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" - integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== - v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" From b701d736ec2dd1e2a24a0fbbdfdf00bd9249e340 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 21:55:11 +0200 Subject: [PATCH 22/41] chore(deps): update devdependency rollup to ^2.10.3 (#5392) Co-authored-by: Renovate Bot --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7c502715b84..3091f329ed1 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "postcss-cli": "^7.1.1", "prettier": "1.14.3", "require-context": "^1.1.0", - "rollup": "^2.10.2", + "rollup": "^2.10.3", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index d156d03529c..8159c1aee25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11350,10 +11350,10 @@ rollup-pluginutils@^2.8.1: dependencies: estree-walker "^0.6.1" -rollup@^2.10.2: - version "2.10.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.2.tgz#9adfcf8ab36861b5b0f8ca7b436f5866e3e9e200" - integrity sha512-tivFM8UXBlYOUqpBYD3pRktYpZvK/eiCQ190eYlrAyrpE/lzkyG2gbawroNdbwmzyUc7Y4eT297xfzv0BDh9qw== +rollup@^2.10.3: + version "2.10.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.10.3.tgz#b024155a93a13412ff547764f8fd4f67fbf64f51" + integrity sha512-Pw4isow3qDZvIozWtEBSZckSxpZgAj11hLijykaO/qZLNq4VGZVZOANBzoWlMabTtg9edQTe3pkLU39PoIkOeg== optionalDependencies: fsevents "~2.1.2" From a1543b297040ea593306ec55d7de5f1e2e776bce Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Mon, 18 May 2020 18:51:54 -0300 Subject: [PATCH 23/41] fi(v-b-toggle): don't check for `evt.defaultPrevened` (closes #5391) (#5396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jacob Müller --- src/directives/toggle/README.md | 11 ++--------- src/directives/toggle/toggle.js | 10 ++++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/directives/toggle/README.md b/src/directives/toggle/README.md index b5deebfefe2..a639933fec2 100644 --- a/src/directives/toggle/README.md +++ b/src/directives/toggle/README.md @@ -93,15 +93,8 @@ trigger element when the target component is closed, and removed when open. As o ## Preventing the target from opening or closing To prevent the trigger element from toggling the target, set the `disabled` prop on `