Skip to content

Commit

Permalink
Merge branch 'main' into gs-base-component-dispose-element-events
Browse files Browse the repository at this point in the history
  • Loading branch information
XhmikosR committed Mar 23, 2021
2 parents ed10458 + b2deaa9 commit 952cf90
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 15 deletions.
10 changes: 8 additions & 2 deletions js/src/offcanvas.js
Expand Up @@ -31,6 +31,7 @@ const NAME = 'offcanvas'
const DATA_KEY = 'bs.offcanvas'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
const ESCAPE_KEY = 'Escape'

const Default = {
Expand All @@ -48,7 +49,8 @@ const DefaultType = {
const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop'
const CLASS_NAME_SHOW = 'show'
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_SHOWN = `shown${EVENT_KEY}`
Expand All @@ -72,7 +74,7 @@ class Offcanvas extends BaseComponent {
super(element)

this._config = this._getConfig(config)
this._isShown = element.classList.contains(CLASS_NAME_SHOW)
this._isShown = false
this._addEventListeners()
}

Expand Down Expand Up @@ -262,6 +264,10 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
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
Expand Down
138 changes: 131 additions & 7 deletions js/tests/unit/offcanvas.spec.js
Expand Up @@ -145,6 +145,44 @@ describe('Offcanvas', () => {
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', () => {
it('should call show method if show class is not present', () => {
Expand All @@ -161,10 +199,12 @@ describe('Offcanvas', () => {
})

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)
offCanvas.show()
expect(offCanvasEl.classList.contains('show')).toBe(true)

spyOn(offCanvas, 'hide')

Expand All @@ -178,11 +218,13 @@ describe('Offcanvas', () => {
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'

spyOn(EventHandler, 'trigger')

const offCanvasEl = fixtureEl.querySelector('div')
const offCanvas = new Offcanvas(offCanvasEl)

offCanvas.show()
expect(offCanvasEl.classList.contains('show')).toBe(true)

spyOn(EventHandler, 'trigger').and.callThrough()
offCanvas.show()

expect(EventHandler.trigger).not.toHaveBeenCalled()
Expand Down Expand Up @@ -226,13 +268,30 @@ describe('Offcanvas', () => {

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', () => {
it('should do nothing if already shown', () => {
fixtureEl.innerHTML = '<div class="offcanvas"></div>'

spyOn(EventHandler, 'trigger')
spyOn(EventHandler, 'trigger').and.callThrough()

const offCanvasEl = fixtureEl.querySelector('div')
const offCanvas = new Offcanvas(offCanvasEl)
Expand All @@ -243,10 +302,11 @@ describe('Offcanvas', () => {
})

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 offCanvas = new Offcanvas(offCanvasEl)
offCanvas.show()

offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
expect(offCanvasEl.classList.contains('show')).toEqual(false)
Expand All @@ -257,10 +317,11 @@ describe('Offcanvas', () => {
})

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 offCanvas = new Offcanvas(offCanvasEl)
offCanvas.show()

const expectEnd = () => {
setTimeout(() => {
Expand Down Expand Up @@ -315,6 +376,52 @@ describe('Offcanvas', () => {

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', () => {
Expand Down Expand Up @@ -432,6 +539,23 @@ describe('Offcanvas', () => {
jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
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', () => {
Expand Down
3 changes: 0 additions & 3 deletions site/assets/scss/_component-examples.scss
Expand Up @@ -206,10 +206,7 @@

.offcanvas {
position: static;
display: block;
height: 200px;
visibility: visible;
transform: translateX(0);
}
}

Expand Down
6 changes: 3 additions & 3 deletions site/content/docs/5.0/components/offcanvas.md
Expand Up @@ -25,10 +25,10 @@ Offcanvas is a sidebar component that can be toggled via JavaScript to appear fr

### 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" >}}
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
{{< example class="bd-example-offcanvas p-0 bg-light overflow-hidden" >}}
<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">
<h5 class="offcanvas-title" id="offcanvasLabel">Offcanvas</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
Expand Down
1 change: 1 addition & 0 deletions site/content/docs/5.0/examples/starter-template/index.html
Expand Up @@ -38,6 +38,7 @@ <h2>Guides</h2>
<ul class="icon-list">
<li><a href="{{< docsref "/getting-started/introduction" >}}">Bootstrap quick start guide</a></li>
<li><a href="{{< docsref "/getting-started/webpack" >}}">Bootstrap Webpack guide</a></li>
<li><a href="{{< docsref "/getting-started/parcel" >}}">Bootstrap Parcel guide</a></li>
<li><a href="{{< docsref "/getting-started/build-tools" >}}">Contributing to Bootstrap</a></li>
</ul>
</div>
Expand Down

0 comments on commit 952cf90

Please sign in to comment.