🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/components/dropdown/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Like to move your menu away from the toggle buttons a bit? Then use the `offset`
number of pixels to push right (or left when negative) from the toggle button:

- Specified as a number of pixels: positive for right shift, negative for left shift.
- Specify the distance in CSS units (i.e. `0.3rem`, `4px`, `1.2em`, etc) passed as a string.
- Specify the distance in CSS units (i.e. `0.3rem`, `4px`, `1.2em`, etc.) passed as a string.

```html
<div>
Expand All @@ -156,12 +156,23 @@ specify a boundary element via the `boundary` prop. Supported values are `'scrol
default), `'viewport'`, `'window'` or a reference to an HTML element. The boundary value is passed
directly to Popper.js's `boundariesElement` configuration option.

**Note:** when `boundary` is any value other than the default of `'scrollParent'`, the style
**Note:** When `boundary` is any value other than the default of `'scrollParent'`, the style
`position: static` is applied to to the dropdown component's root element in order to allow the menu
to "break-out" of its scroll container. In some situations this may affect your layout or
positioning of the dropdown trigger button. In these cases you may need to wrap your dropdown inside
another element.

### Advanced Popper.js configuration

If you need some advanced Popper.js configuration to make dropdowns behave to your needs, you can
use the `popper-opts` prop to pass down a custom configuration object which will be deeply merged
with the BootstrapVue defaults.

Head to the [Popper.js docs](https://popper.js.org/docs/v1/) to see all the configuration options.

**Note**: The props `offset`, `boundary` and `no-flip` may loose their effect when you overwrite the
Popper.js configuration.

## Split button support

Create a split dropdown button, where the left button provides standard `click` event and link
Expand Down
9 changes: 3 additions & 6 deletions src/components/dropdown/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
props,
computed: {
dropdownClasses() {
const { block, split, boundary } = this
const { block, split } = this
return [
this.directionClass,
this.boundaryClass,
{
show: this.visible,
// The 'btn-group' class is required in `split` mode for button alignment
Expand All @@ -111,11 +112,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
'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': 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': boundary !== 'scrollParent' || !boundary
'd-flex': block && split
}
]
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/form-datepicker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ either `min` or `max` (depending on which is closes to today's date).
Use the dropdown props `right`, `dropup`, `dropright`, `dropleft`, `no-flip`, and `offset` to
control the positioning of the popup calendar.

Refer to the [`<b-dropdown>` documentation](/docs/components/dropdown) for details on the effects
and usage of these props.
Refer to the [`<b-dropdown>` positioning section](/docs/components/dropdown#positioning) for details
on the effects and usage of these props.

### Initial open calendar date

Expand Down
4 changes: 2 additions & 2 deletions src/components/form-timepicker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ keep these labels short.
Use the dropdown props `right`, `dropup`, `dropright`, `dropleft`, `no-flip`, and `offset` to
control the positioning of the popup calendar.

Refer to the [`<b-dropdown>` documentation](/docs/components/dropdown) for details on the effects
and usage of these props.
Refer to the [`<b-dropdown>` positioning section](/docs/components/dropdown#positioning) for details
on the effects and usage of these props.

### Button only mode

Expand Down
12 changes: 10 additions & 2 deletions src/components/nav/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ Use `<b-nav-item-dropdown>` to place dropdown items within your nav.
</b-nav>
</div>

<!-- b-nav-dropdown.vue -->
<!-- b-nav-item-dropdown.vue -->
```

Sometimes you want to add your own class names to the generated dropdown toggle button, that by
Expand Down Expand Up @@ -223,6 +223,14 @@ shown. When there are a large number of dropdowns rendered on the same page, per
impacted due to larger overall memory utilization. You can instruct `<b-nav-item-dropdown>` to
render the menu contents only when it is shown by setting the `lazy` prop to true.

### Dropdown placement

Use the dropdown props `right`, `dropup`, `dropright`, `dropleft`, `no-flip`, and `offset` to
control the positioning of `<b-nav-item-dropdown>`.

Refer to the [`<b-dropdown>` positioning section](/docs/components/dropdown#positioning) for details
on the effects and usage of these props.

### Dropdown implementation note

Note that the toggle button is actually rendered as a link `<a>` tag with `role="button"` for
Expand Down Expand Up @@ -438,7 +446,7 @@ add the role to the `<b-nav>` itself, as this would prevent it from being announ
list by assistive technologies.

When using a `<b-nav-item-dropdown>` in your `<b-nav>`, be sure to assign a unique `id` prop value
to the `<b-nav-dropdown>` so that the appropriate `aria-*` attributes can be automatically
to the `<b-nav-item-dropdown>` so that the appropriate `aria-*` attributes can be automatically
generated.

### Tabbed interface accessibility
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/nav-item-dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
return true
},
dropdownClasses() {
return [this.directionClass, { show: this.visible }]
return [this.directionClass, this.boundaryClass, { show: this.visible }]
},
menuClasses() {
return [
Expand Down
21 changes: 15 additions & 6 deletions src/mixins/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BvEvent } from '../utils/bv-event.class'
import { attemptFocus, closest, contains, isVisible, requestAF, selectAll } from '../utils/dom'
import { stopEvent } from '../utils/events'
import { isNull } from '../utils/inspect'
import { mergeDeep } from '../utils/object'
import { HTMLElement } from '../utils/safe-types'
import { warn } from '../utils/warn'
import clickOutMixin from './click-out'
Expand Down Expand Up @@ -68,17 +69,17 @@ export const commonProps = {
default: false
},
offset: {
// Number of pixels to offset menu, or a CSS unit value (i.e. 1px, 1rem, etc)
// Number of pixels to offset menu, or a CSS unit value (i.e. `1px`, `1rem`, etc.)
type: [Number, String],
default: 0
},
noFlip: {
// Disable auto-flipping of menu from bottom<=>top
// Disable auto-flipping of menu from bottom <=> top
type: Boolean,
default: false
},
popperOpts: {
// type: Object,
type: Object,
default: () => {}
},
boundary: {
Expand Down Expand Up @@ -128,6 +129,13 @@ export default {
return 'dropleft'
}
return ''
},
boundaryClass() {
// 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
const { boundary } = this
return boundary !== 'scrollParent' || !boundary ? 'position-static' : ''
}
},
watch: {
Expand Down Expand Up @@ -267,10 +275,11 @@ export default {
flip: { enabled: !this.noFlip }
}
}
if (this.boundary) {
popperConfig.modifiers.preventOverflow = { boundariesElement: this.boundary }
const boundariesElement = this.boundary
if (boundariesElement) {
popperConfig.modifiers.preventOverflow = { boundariesElement }
}
return { ...popperConfig, ...(this.popperOpts || {}) }
return mergeDeep(popperConfig, this.popperOpts || {})
},
// Turn listeners on/off while open
whileOpenListen(isOpen) {
Expand Down
6 changes: 5 additions & 1 deletion src/utils/bv-form-btn-label-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,10 @@ export const BVFormBtnLabelControl = /*#__PURE__*/ Vue.extend({
on: {
// Disable bubbling of the click event to
// prevent menu from closing and re-opening
'!click': stopEvent

'!click': /* istanbul ignore next */ evt => {
stopEvent(evt, { preventDefault: false })
}
}
},
[
Expand All @@ -281,6 +284,7 @@ export const BVFormBtnLabelControl = /*#__PURE__*/ Vue.extend({
staticClass: 'b-form-btn-label-control dropdown',
class: [
this.directionClass,
this.boundaryClass,
{
'btn-group': buttonOnly,
'form-control': !buttonOnly,
Expand Down
9 changes: 7 additions & 2 deletions src/utils/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ export const eventOnOff = (on, ...args) => {
}

// Utility method to prevent the default event handling and propagation
export const stopEvent = (evt, { propagation = true, immediatePropagation = false } = {}) => {
evt.preventDefault()
export const stopEvent = (
evt,
{ preventDefault = true, propagation = true, immediatePropagation = false } = {}
) => {
if (preventDefault) {
evt.preventDefault()
}
if (propagation) {
evt.stopPropagation()
}
Expand Down
20 changes: 20 additions & 0 deletions src/utils/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ export const omit = (obj, props) =>
.filter(key => props.indexOf(key) === -1)
.reduce((result, key) => ({ ...result, [key]: obj[key] }), {})

/**
* Merges two object deeply together
* @link https://gist.github.com/Salakar/1d7137de9cb8b704e48a
*/
export const mergeDeep = (target, source) => {
if (isObject(target) && isObject(source)) {
keys(source).forEach(key => {
if (isObject(source[key])) {
if (!target[key] || !isObject(target[key])) {
target[key] = source[key]
}
mergeDeep(target[key], source[key])
} else {
assign(target, { [key]: source[key] })
}
})
}
return target
}

/**
* Convenience method to create a read-only descriptor
*/
Expand Down
44 changes: 43 additions & 1 deletion src/utils/object.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pick, omit } from './object'
import { pick, omit, mergeDeep } from './object'

describe('utils/object', () => {
it('pick() works', async () => {
Expand All @@ -16,4 +16,46 @@ describe('utils/object', () => {
expect(omit(obj, Object.keys(obj))).toEqual({})
expect(omit(obj, [])).toEqual(obj)
})

it('mergeDeep() works', async () => {
const A = {
a: {
loc: 'Earth',
title: 'Hello World',
type: 'Planet',
deeper: {
map: new Map([['a', 'AAA'], ['b', 'BBB']]),
mapId: 15473
}
}
}
const B = {
a: {
type: 'Star',
deeper: {
mapId: 9999,
alt_map: new Map([['x', 'XXXX'], ['y', 'YYYY']])
}
}
}

const C = mergeDeep(A, B)
const D = mergeDeep({ a: 1 }, { b: { c: { d: { e: 12345 } } } })
const E = mergeDeep({ b: { c: 'hallo' } }, { b: { c: { d: { e: 12345 } } } })
const F = mergeDeep(
{ b: { c: { d: { e: 12345 } }, d: 'dag', f: 'one' } },
{ b: { c: 'hallo', e: 'ok', f: 'two' } }
)

expect(C.a.type).toEqual('Star')
expect(C.a.deeper.alt_map.get('x')).toEqual('XXXX')
expect(C.a.deeper.map.get('b')).toEqual('BBB')
expect(D.a).toEqual(1)
expect(D.b.c.d.e).toEqual(12345)
expect(E.b.c.d.e).toEqual(12345)
expect(F.b.c).toEqual('hallo')
expect(F.b.d).toEqual('dag')
expect(F.b.e).toEqual('ok')
expect(F.b.f).toEqual('two')
})
})