mirror of https://github.com/twbs/bootstrap.git
Allow offcanvas to be initialized in open state (#33382)
* Update docs to use new .show behavior and clarify some copy for first example Co-authored-by: Mark Otto <markdotto@gmail.com> Co-authored-by: XhmikosR <xhmikosr@gmail.com>
This commit is contained in:
parent
d9da43f3cc
commit
1c02ef4f97
|
@ -31,6 +31,7 @@ const NAME = 'offcanvas'
|
||||||
const DATA_KEY = 'bs.offcanvas'
|
const DATA_KEY = 'bs.offcanvas'
|
||||||
const EVENT_KEY = `.${DATA_KEY}`
|
const EVENT_KEY = `.${DATA_KEY}`
|
||||||
const DATA_API_KEY = '.data-api'
|
const DATA_API_KEY = '.data-api'
|
||||||
|
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
|
||||||
const ESCAPE_KEY = 'Escape'
|
const ESCAPE_KEY = 'Escape'
|
||||||
|
|
||||||
const Default = {
|
const Default = {
|
||||||
|
@ -48,7 +49,8 @@ const DefaultType = {
|
||||||
const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop'
|
const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop'
|
||||||
const CLASS_NAME_SHOW = 'show'
|
const CLASS_NAME_SHOW = 'show'
|
||||||
const CLASS_NAME_TOGGLING = 'offcanvas-toggling'
|
const CLASS_NAME_TOGGLING = 'offcanvas-toggling'
|
||||||
const ACTIVE_SELECTOR = `.offcanvas.show, .${CLASS_NAME_TOGGLING}`
|
const OPEN_SELECTOR = '.offcanvas.show'
|
||||||
|
const ACTIVE_SELECTOR = `${OPEN_SELECTOR}, .${CLASS_NAME_TOGGLING}`
|
||||||
|
|
||||||
const EVENT_SHOW = `show${EVENT_KEY}`
|
const EVENT_SHOW = `show${EVENT_KEY}`
|
||||||
const EVENT_SHOWN = `shown${EVENT_KEY}`
|
const EVENT_SHOWN = `shown${EVENT_KEY}`
|
||||||
|
@ -72,7 +74,7 @@ class Offcanvas extends BaseComponent {
|
||||||
super(element)
|
super(element)
|
||||||
|
|
||||||
this._config = this._getConfig(config)
|
this._config = this._getConfig(config)
|
||||||
this._isShown = element.classList.contains(CLASS_NAME_SHOW)
|
this._isShown = false
|
||||||
this._addEventListeners()
|
this._addEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +264,10 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
|
||||||
data.toggle(this)
|
data.toggle(this)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
|
||||||
|
SelectorEngine.find(OPEN_SELECTOR).forEach(el => (Data.get(el, DATA_KEY) || new Offcanvas(el)).show())
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ------------------------------------------------------------------------
|
* ------------------------------------------------------------------------
|
||||||
* jQuery
|
* jQuery
|
||||||
|
|
|
@ -145,6 +145,44 @@ describe('Offcanvas', () => {
|
||||||
expect(offCanvas._config.scroll).toEqual(false)
|
expect(offCanvas._config.scroll).toEqual(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('options', () => {
|
||||||
|
it('if scroll is enabled, should allow body to scroll while offcanvas is open', done => {
|
||||||
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
|
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||||
|
const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })
|
||||||
|
const initialOverFlow = document.body.style.overflow
|
||||||
|
|
||||||
|
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||||
|
expect(document.body.style.overflow).toEqual(initialOverFlow)
|
||||||
|
|
||||||
|
offCanvas.hide()
|
||||||
|
})
|
||||||
|
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||||
|
expect(document.body.style.overflow).toEqual(initialOverFlow)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
offCanvas.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('if scroll is disabled, should not allow body to scroll while offcanvas is open', done => {
|
||||||
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
|
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||||
|
const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })
|
||||||
|
|
||||||
|
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||||
|
expect(document.body.style.overflow).toEqual('hidden')
|
||||||
|
|
||||||
|
offCanvas.hide()
|
||||||
|
})
|
||||||
|
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||||
|
expect(document.body.style.overflow).toEqual('auto')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
offCanvas.show()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('toggle', () => {
|
describe('toggle', () => {
|
||||||
it('should call show method if show class is not present', () => {
|
it('should call show method if show class is not present', () => {
|
||||||
|
@ -161,10 +199,12 @@ describe('Offcanvas', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call hide method if show class is present', () => {
|
it('should call hide method if show class is present', () => {
|
||||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
const offCanvasEl = fixtureEl.querySelector('.show')
|
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||||
const offCanvas = new Offcanvas(offCanvasEl)
|
const offCanvas = new Offcanvas(offCanvasEl)
|
||||||
|
offCanvas.show()
|
||||||
|
expect(offCanvasEl.classList.contains('show')).toBe(true)
|
||||||
|
|
||||||
spyOn(offCanvas, 'hide')
|
spyOn(offCanvas, 'hide')
|
||||||
|
|
||||||
|
@ -178,11 +218,13 @@ describe('Offcanvas', () => {
|
||||||
it('should do nothing if already shown', () => {
|
it('should do nothing if already shown', () => {
|
||||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
||||||
|
|
||||||
spyOn(EventHandler, 'trigger')
|
|
||||||
|
|
||||||
const offCanvasEl = fixtureEl.querySelector('div')
|
const offCanvasEl = fixtureEl.querySelector('div')
|
||||||
const offCanvas = new Offcanvas(offCanvasEl)
|
const offCanvas = new Offcanvas(offCanvasEl)
|
||||||
|
|
||||||
|
offCanvas.show()
|
||||||
|
expect(offCanvasEl.classList.contains('show')).toBe(true)
|
||||||
|
|
||||||
|
spyOn(EventHandler, 'trigger').and.callThrough()
|
||||||
offCanvas.show()
|
offCanvas.show()
|
||||||
|
|
||||||
expect(EventHandler.trigger).not.toHaveBeenCalled()
|
expect(EventHandler.trigger).not.toHaveBeenCalled()
|
||||||
|
@ -226,13 +268,30 @@ describe('Offcanvas', () => {
|
||||||
|
|
||||||
offCanvas.show()
|
offCanvas.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('on window load, should make visible an offcanvas element, if its markup contains class "show"', done => {
|
||||||
|
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
||||||
|
|
||||||
|
const offCanvasEl = fixtureEl.querySelector('div')
|
||||||
|
spyOn(Offcanvas.prototype, 'show').and.callThrough()
|
||||||
|
|
||||||
|
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
window.dispatchEvent(createEvent('load'))
|
||||||
|
|
||||||
|
const instance = Offcanvas.getInstance(offCanvasEl)
|
||||||
|
expect(instance).not.toBeNull()
|
||||||
|
expect(Offcanvas.prototype.show).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('hide', () => {
|
describe('hide', () => {
|
||||||
it('should do nothing if already shown', () => {
|
it('should do nothing if already shown', () => {
|
||||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
spyOn(EventHandler, 'trigger')
|
spyOn(EventHandler, 'trigger').and.callThrough()
|
||||||
|
|
||||||
const offCanvasEl = fixtureEl.querySelector('div')
|
const offCanvasEl = fixtureEl.querySelector('div')
|
||||||
const offCanvas = new Offcanvas(offCanvasEl)
|
const offCanvas = new Offcanvas(offCanvasEl)
|
||||||
|
@ -243,10 +302,11 @@ describe('Offcanvas', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should hide a shown element', done => {
|
it('should hide a shown element', done => {
|
||||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
const offCanvasEl = fixtureEl.querySelector('div')
|
const offCanvasEl = fixtureEl.querySelector('div')
|
||||||
const offCanvas = new Offcanvas(offCanvasEl)
|
const offCanvas = new Offcanvas(offCanvasEl)
|
||||||
|
offCanvas.show()
|
||||||
|
|
||||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||||
expect(offCanvasEl.classList.contains('show')).toEqual(false)
|
expect(offCanvasEl.classList.contains('show')).toEqual(false)
|
||||||
|
@ -257,10 +317,11 @@ describe('Offcanvas', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not fire hidden when hide is prevented', done => {
|
it('should not fire hidden when hide is prevented', done => {
|
||||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||||
|
|
||||||
const offCanvasEl = fixtureEl.querySelector('div')
|
const offCanvasEl = fixtureEl.querySelector('div')
|
||||||
const offCanvas = new Offcanvas(offCanvasEl)
|
const offCanvas = new Offcanvas(offCanvasEl)
|
||||||
|
offCanvas.show()
|
||||||
|
|
||||||
const expectEnd = () => {
|
const expectEnd = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -315,6 +376,52 @@ describe('Offcanvas', () => {
|
||||||
|
|
||||||
expect(Offcanvas.prototype.toggle).not.toHaveBeenCalled()
|
expect(Offcanvas.prototype.toggle).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not call toggle if another offcanvas is open', done => {
|
||||||
|
fixtureEl.innerHTML = [
|
||||||
|
'<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2" ></button>',
|
||||||
|
'<div id="offcanvas1" class="offcanvas"></div>',
|
||||||
|
'<div id="offcanvas2" class="offcanvas"></div>'
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
const trigger2 = fixtureEl.querySelector('#btn2')
|
||||||
|
const offcanvasEl1 = document.querySelector('#offcanvas1')
|
||||||
|
const offcanvasEl2 = document.querySelector('#offcanvas2')
|
||||||
|
const offcanvas1 = new Offcanvas(offcanvasEl1)
|
||||||
|
|
||||||
|
offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {
|
||||||
|
trigger2.click()
|
||||||
|
})
|
||||||
|
offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {
|
||||||
|
expect(Offcanvas.getInstance(offcanvasEl2)).toEqual(null)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
offcanvas1.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should focus on trigger element after closing offcanvas', done => {
|
||||||
|
fixtureEl.innerHTML = [
|
||||||
|
'<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas" ></button>',
|
||||||
|
'<div id="offcanvas" class="offcanvas"></div>'
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
const trigger = fixtureEl.querySelector('#btn')
|
||||||
|
const offcanvasEl = fixtureEl.querySelector('#offcanvas')
|
||||||
|
const offcanvas = new Offcanvas(offcanvasEl)
|
||||||
|
spyOn(trigger, 'focus')
|
||||||
|
|
||||||
|
offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||||
|
offcanvas.hide()
|
||||||
|
})
|
||||||
|
offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(trigger.focus).toHaveBeenCalled()
|
||||||
|
done()
|
||||||
|
}, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
trigger.click()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('jQueryInterface', () => {
|
describe('jQueryInterface', () => {
|
||||||
|
@ -432,6 +539,23 @@ describe('Offcanvas', () => {
|
||||||
jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
|
jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
|
||||||
expect(Offcanvas.prototype.show).toHaveBeenCalled()
|
expect(Offcanvas.prototype.show).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create a offcanvas with given config', () => {
|
||||||
|
fixtureEl.innerHTML = '<div></div>'
|
||||||
|
|
||||||
|
const div = fixtureEl.querySelector('div')
|
||||||
|
|
||||||
|
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||||
|
jQueryMock.elements = [div]
|
||||||
|
|
||||||
|
jQueryMock.fn.offcanvas.call(jQueryMock, { scroll: true })
|
||||||
|
spyOn(Offcanvas.prototype, 'constructor')
|
||||||
|
expect(Offcanvas.prototype.constructor).not.toHaveBeenCalledWith(div, { scroll: true })
|
||||||
|
|
||||||
|
const offcanvas = Offcanvas.getInstance(div)
|
||||||
|
expect(offcanvas).toBeDefined()
|
||||||
|
expect(offcanvas._config.scroll).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getInstance', () => {
|
describe('getInstance', () => {
|
||||||
|
|
|
@ -206,10 +206,7 @@
|
||||||
|
|
||||||
.offcanvas {
|
.offcanvas {
|
||||||
position: static;
|
position: static;
|
||||||
display: block;
|
|
||||||
height: 200px;
|
height: 200px;
|
||||||
visibility: visible;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,10 @@ Offcanvas is a sidebar component that can be toggled via JavaScript to appear fr
|
||||||
|
|
||||||
### Offcanvas components
|
### Offcanvas components
|
||||||
|
|
||||||
Below is a _static_ offcanvas example (meaning its `position`, `display`, and `visibility` have been overridden). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action.
|
Below is an offcanvas example that is shown by default (via `.show` on `.offcanvas`). Offcanvas includes support for a header with a close button and an optional body class for some initial `padding`. We suggest that you include offcanvas headers with dismiss actions whenever possible, or provide an explicit dismiss action.
|
||||||
|
|
||||||
{{< example class="bd-example-offcanvas p-0 bg-light" >}}
|
{{< example class="bd-example-offcanvas p-0 bg-light overflow-hidden" >}}
|
||||||
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
|
<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel" data-bs-backdrop="false" data-bs-scroll="true">
|
||||||
<div class="offcanvas-header">
|
<div class="offcanvas-header">
|
||||||
<h5 class="offcanvas-title" id="offcanvasLabel">Offcanvas</h5>
|
<h5 class="offcanvas-title" id="offcanvasLabel">Offcanvas</h5>
|
||||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
|
Loading…
Reference in New Issue