diff --git a/js/src/dropdown.js b/js/src/dropdown.js index bada537c9c8e..66ff8cc4fcf7 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -72,7 +72,7 @@ const PLACEMENT_RIGHT = isRTL ? 'left-start' : 'right-start' const PLACEMENT_LEFT = isRTL ? 'right-start' : 'left-start' const Default = { - offset: 0, + offset: [0, 0], flip: true, boundary: 'clippingParents', reference: 'toggle', @@ -81,7 +81,7 @@ const Default = { } const DefaultType = { - offset: '(number|string|function)', + offset: '(array|string|function)', flip: 'boolean', boundary: '(string|element)', reference: '(string|element|object)', @@ -298,6 +298,20 @@ class Dropdown extends BaseComponent { return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null } + _getOffset() { + const { offset } = this._config + + if (typeof offset === 'string') { + return offset.split(',').map(val => Number.parseInt(val, 10)) + } + + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element) + } + + return offset + } + _getPopperConfig() { const popperConfig = { placement: this._getPlacement(), @@ -313,6 +327,12 @@ class Dropdown extends BaseComponent { options: { fallbackPlacements: ['top', 'right', 'bottom', 'left'] } + }, + { + name: 'offset', + options: { + offset: this._getOffset() + } }] } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 909cb0f8a7d5..6d85fde26942 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -50,6 +50,7 @@ const DefaultType = { html: 'boolean', selector: '(string|boolean)', placement: '(string|function)', + offset: '(array|string|function)', container: '(string|element|boolean)', fallbackPlacements: 'array', boundary: '(string|element)', @@ -80,6 +81,7 @@ const Default = { html: false, selector: false, placement: 'top', + offset: [0, 0], container: false, fallbackPlacements: ['top', 'right', 'bottom', 'left'], boundary: 'clippingParents', @@ -473,6 +475,20 @@ class Tooltip extends BaseComponent { return context } + _getOffset() { + const { offset } = this.config + + if (typeof offset === 'string') { + return offset.split(',').map(val => Number.parseInt(val, 10)) + } + + if (typeof offset === 'function') { + return popperData => offset(popperData, this._element) + } + + return offset + } + _getPopperConfig(attachment) { const defaultBsConfig = { placement: attachment, @@ -484,6 +500,12 @@ class Tooltip extends BaseComponent { fallbackPlacements: this.config.fallbackPlacements } }, + { + name: 'offset', + options: { + offset: this._getOffset() + } + }, { name: 'preventOverflow', options: { diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js index cc41396034cb..8b477ba38f80 100644 --- a/js/tests/unit/dropdown.spec.js +++ b/js/tests/unit/dropdown.spec.js @@ -54,6 +54,54 @@ describe('Dropdown', () => { expect(dropdown.toggle).toHaveBeenCalled() }) + it('should create offset modifier correctly when offset option is a function', done => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20]) + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdown = new Dropdown(btnDropdown, { + offset: getOffset, + popperConfig: { + onFirstUpdate: state => { + expect(getOffset).toHaveBeenCalledWith({ + popper: state.rects.popper, + reference: state.rects.reference, + placement: state.placement + }, btnDropdown) + done() + } + } + }) + const offset = dropdown._getOffset() + + expect(typeof offset).toEqual('function') + + dropdown.show() + }) + + it('should create offset modifier correctly when offset option is a string into data attribute', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdown = new Dropdown(btnDropdown) + + expect(dropdown._getOffset()).toEqual([10, 20]) + }) + it('should allow to pass config to Popper with `popperConfig`', () => { fixtureEl.innerHTML = [ '