-
-
Notifications
You must be signed in to change notification settings - Fork 78.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Offcanvas as component #29017
Merged
Merged
Offcanvas as component #29017
Changes from all commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
b4b99d7
Add a new offcanvas component
GeoSot 15ea3fc
offcanvas.js: switch to string constants and `event.key`
XhmikosR b3dce19
Remove unneeded code
XhmikosR f504db0
Sass optimizations
MartijnCuppens bcc2b96
Fixes
GeoSot 2b57bbe
Wording tweaks
XhmikosR de607c7
update
GeoSot 7d12110
update tests and offcanvas class
GeoSot 18a4aad
separate scrollbar functionality and use it in offcanvas
GeoSot 3960459
Update .bundlewatch.config.json
GeoSot 722c1c8
fix focus
GeoSot 6f25645
update btn-close / fix focus on close
GeoSot 4a99294
add aria-modal and role
GeoSot 56e245b
move common code to reusable functions
GeoSot 22f538e
add aria-labelledby
GeoSot 2f9a60f
Replace lorem ipsum text
GeoSot cbba2dd
fix focus when offcanvas is closed
GeoSot f0d9ae9
updates
GeoSot cafb8e7
revert modal, add tests for scrollbar
GeoSot 334cf93
show backdrop by default
GeoSot 3b48c6a
Update offcanvas.md
XhmikosR bb42e9d
Update offcanvas CSS to better match modals
mdo 3de767a
Revamp offcanvas docs
mdo aad78ec
Add .offcanvas-title instead of .modal-title
mdo 95cd2d1
Rename offcanvas example to offcanvas-navbar to reflect it's purpose
mdo 5c20649
labelledby references title and not header
mdo 6f40385
Add default shadow to offcanvas
mdo e25e9d1
enable offcanvas-body to fill all the remaining wrapper area
GeoSot e07cf43
Be more descriptive, on Accessibility area
GeoSot 8445e36
remove redundant classes
GeoSot 97db642
ensure in case of an already open offcanvas, not to open another one
GeoSot a06dbbb
bring back backdrop|scroll combinations
GeoSot 0228f65
Update offcanvas.md
XhmikosR 3eb6b34
bring back toggling class
GeoSot f7f35fb
refactor scrollbar method, plus tests
GeoSot df053ae
add check if element is not full-width, according to #30621
GeoSot 311e97f
revert all in modal
GeoSot 39babe2
use documentElement innerWidth
GeoSot aaef5b5
Rename classes to -start and -end
mdo a1a3fea
omit some things on scrollbar
GeoSot f9a01a1
PASS BrowserStack tests
GeoSot 9c04ec8
Rename '_handleClosing' to '_addEventListeners'
GeoSot b2b3d04
change pipe usage to comma
GeoSot d0a4cbb
Data.get
XhmikosR File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
/** | ||
* -------------------------------------------------------------------------- | ||
* Bootstrap (v5.0.0-beta2): offcanvas.js | ||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | ||
* -------------------------------------------------------------------------- | ||
*/ | ||
|
||
import { | ||
defineJQueryPlugin, | ||
getElementFromSelector, | ||
getSelectorFromElement, | ||
getTransitionDurationFromElement, | ||
isVisible | ||
} from './util/index' | ||
import { hide as scrollBarHide, reset as scrollBarReset } from './util/scrollbar' | ||
import Data from './dom/data' | ||
import EventHandler from './dom/event-handler' | ||
import BaseComponent from './base-component' | ||
import SelectorEngine from './dom/selector-engine' | ||
|
||
/** | ||
* ------------------------------------------------------------------------ | ||
* Constants | ||
* ------------------------------------------------------------------------ | ||
*/ | ||
|
||
const NAME = 'offcanvas' | ||
const DATA_KEY = 'bs.offcanvas' | ||
const EVENT_KEY = `.${DATA_KEY}` | ||
const DATA_API_KEY = '.data-api' | ||
const ESCAPE_KEY = 'Escape' | ||
const DATA_BODY_ACTIONS = 'data-bs-body' | ||
|
||
const CLASS_NAME_BACKDROP_BODY = 'offcanvas-backdrop' | ||
const CLASS_NAME_DISABLED = 'disabled' | ||
const CLASS_NAME_SHOW = 'show' | ||
const CLASS_NAME_TOGGLING = 'offcanvas-toggling' | ||
const ACTIVE_SELECTOR = `.offcanvas.show, .${CLASS_NAME_TOGGLING}` | ||
|
||
const EVENT_SHOW = `show${EVENT_KEY}` | ||
const EVENT_SHOWN = `shown${EVENT_KEY}` | ||
const EVENT_HIDE = `hide${EVENT_KEY}` | ||
const EVENT_HIDDEN = `hidden${EVENT_KEY}` | ||
const EVENT_FOCUSIN = `focusin${EVENT_KEY}` | ||
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` | ||
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` | ||
|
||
const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="offcanvas"]' | ||
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="offcanvas"]' | ||
|
||
/** | ||
* ------------------------------------------------------------------------ | ||
* Class Definition | ||
* ------------------------------------------------------------------------ | ||
*/ | ||
|
||
class OffCanvas extends BaseComponent { | ||
constructor(element) { | ||
super(element) | ||
|
||
this._isShown = element.classList.contains(CLASS_NAME_SHOW) | ||
this._bodyOptions = element.getAttribute(DATA_BODY_ACTIONS) || '' | ||
this._addEventListeners() | ||
} | ||
|
||
// Public | ||
|
||
toggle(relatedTarget) { | ||
return this._isShown ? this.hide() : this.show(relatedTarget) | ||
} | ||
|
||
show(relatedTarget) { | ||
if (this._isShown) { | ||
return | ||
} | ||
|
||
const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget }) | ||
|
||
if (showEvent.defaultPrevented) { | ||
return | ||
} | ||
|
||
this._isShown = true | ||
this._element.style.visibility = 'visible' | ||
|
||
if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { | ||
document.body.classList.add(CLASS_NAME_BACKDROP_BODY) | ||
} | ||
|
||
if (!this._bodyOptionsHas('scroll')) { | ||
scrollBarHide() | ||
} | ||
|
||
this._element.classList.add(CLASS_NAME_TOGGLING) | ||
this._element.removeAttribute('aria-hidden') | ||
this._element.setAttribute('aria-modal', true) | ||
this._element.setAttribute('role', 'dialog') | ||
this._element.classList.add(CLASS_NAME_SHOW) | ||
|
||
const completeCallBack = () => { | ||
this._element.classList.remove(CLASS_NAME_TOGGLING) | ||
EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) | ||
this._enforceFocusOnElement(this._element) | ||
} | ||
|
||
setTimeout(completeCallBack, getTransitionDurationFromElement(this._element)) | ||
} | ||
|
||
hide() { | ||
if (!this._isShown) { | ||
return | ||
} | ||
|
||
const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE) | ||
|
||
if (hideEvent.defaultPrevented) { | ||
return | ||
} | ||
|
||
this._element.classList.add(CLASS_NAME_TOGGLING) | ||
EventHandler.off(document, EVENT_FOCUSIN) | ||
this._element.blur() | ||
this._isShown = false | ||
this._element.classList.remove(CLASS_NAME_SHOW) | ||
|
||
const completeCallback = () => { | ||
this._element.setAttribute('aria-hidden', true) | ||
this._element.removeAttribute('aria-modal') | ||
this._element.removeAttribute('role') | ||
this._element.style.visibility = 'hidden' | ||
|
||
if (this._bodyOptionsHas('backdrop') || !this._bodyOptions.length) { | ||
document.body.classList.remove(CLASS_NAME_BACKDROP_BODY) | ||
} | ||
|
||
if (!this._bodyOptionsHas('scroll')) { | ||
scrollBarReset() | ||
} | ||
|
||
EventHandler.trigger(this._element, EVENT_HIDDEN) | ||
this._element.classList.remove(CLASS_NAME_TOGGLING) | ||
} | ||
|
||
setTimeout(completeCallback, getTransitionDurationFromElement(this._element)) | ||
} | ||
|
||
_enforceFocusOnElement(element) { | ||
EventHandler.off(document, EVENT_FOCUSIN) // guard against infinite focus loop | ||
EventHandler.on(document, EVENT_FOCUSIN, event => { | ||
if (document !== event.target && | ||
element !== event.target && | ||
!element.contains(event.target)) { | ||
element.focus() | ||
} | ||
}) | ||
element.focus() | ||
} | ||
|
||
_bodyOptionsHas(option) { | ||
return this._bodyOptions.split(',').includes(option) | ||
} | ||
|
||
_addEventListeners() { | ||
EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide()) | ||
|
||
EventHandler.on(document, 'keydown', event => { | ||
if (event.key === ESCAPE_KEY) { | ||
this.hide() | ||
} | ||
}) | ||
|
||
EventHandler.on(document, EVENT_CLICK_DATA_API, event => { | ||
const target = SelectorEngine.findOne(getSelectorFromElement(event.target)) | ||
if (!this._element.contains(event.target) && target !== this._element) { | ||
this.hide() | ||
} | ||
}) | ||
} | ||
|
||
// Static | ||
|
||
static jQueryInterface(config) { | ||
return this.each(function () { | ||
const data = Data.get(this, DATA_KEY) || new OffCanvas(this) | ||
|
||
if (typeof config === 'string') { | ||
if (typeof data[config] === 'undefined') { | ||
throw new TypeError(`No method named "${config}"`) | ||
} | ||
|
||
GeoSot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data[config](this) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
/** | ||
* ------------------------------------------------------------------------ | ||
* Data Api implementation | ||
* ------------------------------------------------------------------------ | ||
*/ | ||
|
||
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) { | ||
const target = getElementFromSelector(this) | ||
|
||
if (['A', 'AREA'].includes(this.tagName)) { | ||
GeoSot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
event.preventDefault() | ||
} | ||
|
||
if (this.disabled || this.classList.contains(CLASS_NAME_DISABLED)) { | ||
return | ||
} | ||
|
||
EventHandler.one(target, EVENT_HIDDEN, () => { | ||
// focus on trigger when it is closed | ||
if (isVisible(this)) { | ||
this.focus() | ||
} | ||
}) | ||
|
||
// avoid conflict when clicking a toggler of an offcanvas, while another is open | ||
const allReadyOpen = SelectorEngine.findOne(ACTIVE_SELECTOR) | ||
if (allReadyOpen && allReadyOpen !== target) { | ||
return | ||
} | ||
|
||
const data = Data.get(target, DATA_KEY) || new OffCanvas(target) | ||
data.toggle(this) | ||
}) | ||
|
||
/** | ||
* ------------------------------------------------------------------------ | ||
* jQuery | ||
* ------------------------------------------------------------------------ | ||
*/ | ||
|
||
defineJQueryPlugin(NAME, OffCanvas) | ||
|
||
export default OffCanvas |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better if you can accept this option in the configuration object (Like other plugins) 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Rohit. I really would prefer it to keep it simple right now, in order to proceed and to avoid more mess.
If you find it so important, I can change it after. I am not very confident with the
Config
object, as every component uses it, in a different way