]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Allow offcanvas to be initialized in open state (#33382)
authorGeoSot <geo.sotis@gmail.com>
Tue, 23 Mar 2021 06:22:59 +0000 (08:22 +0200)
committerGitHub <noreply@github.com>
Tue, 23 Mar 2021 06:22:59 +0000 (08:22 +0200)
* 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>
js/src/offcanvas.js
js/tests/unit/offcanvas.spec.js
site/assets/scss/_component-examples.scss
site/content/docs/5.0/components/offcanvas.md

index 4b98565e2ce0ce2bc8f5f0d92b328dbdbb99fdd8..1824b3e3b49cecad9c4615d1b52d7fbea86408fc 100644 (file)
@@ -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 = {
@@ -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}`
@@ -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()
   }
 
@@ -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
index 4fb6c17ecb9cb5763f18ba1dd7ffb05d35ba9b46..0122d4dff33ce0b83e1bacb808c73e8cba350adb 100644 (file)
@@ -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', () => {
@@ -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')
 
@@ -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()
@@ -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)
@@ -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)
@@ -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(() => {
@@ -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', () => {
@@ -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', () => {
index 91b5bbfd3e9a626e68cd2a29ed5df8d4eb125d6d..534f2307d15ca643de794944ece72650c31c0045 100644 (file)
 
   .offcanvas {
     position: static;
-    display: block;
     height: 200px;
-    visibility: visible;
-    transform: translateX(0);
   }
 }
 
index 347242bb93515f98a11f05de03d36bf379f8fe07..0e4aa1aa03a9d4eb36666d493ab520eb52e7a737 100644 (file)
@@ -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>