From 44a6cd724c0a5c5247492fdb8db7d4df4705641e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 1 Dec 2021 14:53:56 +0200 Subject: Tooltip: remove leftover method (#35440) Remove a leftover after #32692 Co-authored-by: XhmikosR --- js/src/tooltip.js | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 29be4d8d2..d0b43dd04 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -398,18 +398,6 @@ class Tooltip extends BaseComponent { return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('title') } - updateAttachment(attachment) { - if (attachment === 'right') { - return 'end' - } - - if (attachment === 'left') { - return 'start' - } - - return attachment - } - // Private _initializeOnDelegatedTarget(event, context) { return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) -- cgit v1.2.3 From cab62af2e6ecddbadbf799e00f911c2b342d93b2 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 1 Dec 2021 15:10:10 +0200 Subject: Fix popover arrow & tooltip template after the `setContent` addition (#35441) --- js/src/tooltip.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index d0b43dd04..b09ab0d0c 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -38,7 +38,6 @@ const CLASS_NAME_SHOW = 'show' const HOVER_STATE_SHOW = 'show' const HOVER_STATE_OUT = 'out' -const SELECTOR_TOOLTIP_ARROW = '.tooltip-arrow' const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` @@ -333,15 +332,23 @@ class Tooltip extends BaseComponent { } getTipElement() { - if (this.tip) { - return this.tip + if (!this.tip) { + this.tip = this._createTipElement(this._getContentForTemplate()) } - const templateFactory = this._getTemplateFactory(this._getContentForTemplate()) + return this.tip + } + + _createTipElement(content) { + const tip = this._getTemplateFactory(content).toHtml() + + // todo: remove this check on v6 + if (!tip) { + return null + } - const tip = templateFactory.toHtml() tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) - // todo on v6 the following can be done on css only + // todo: on v6 the following can be achieved with CSS only tip.classList.add(`bs-${this.constructor.NAME}-auto`) const tipId = getUID(this.constructor.NAME).toString() @@ -352,8 +359,7 @@ class Tooltip extends BaseComponent { tip.classList.add(CLASS_NAME_FADE) } - this.tip = tip - return this.tip + return tip } setContent(content) { @@ -361,11 +367,11 @@ class Tooltip extends BaseComponent { if (this.tip) { isShown = this.tip.classList.contains(CLASS_NAME_SHOW) this.tip.remove() + this.tip = null } this._disposePopper() - - this.tip = this._getTemplateFactory(content).toHtml() + this.tip = this._createTipElement(content) if (isShown) { this.show() @@ -446,7 +452,7 @@ class Tooltip extends BaseComponent { { name: 'arrow', options: { - element: SELECTOR_TOOLTIP_ARROW + element: `.${this.constructor.NAME}-arrow` } } ] -- cgit v1.2.3 From 137b3249304b9ffeb76c72b7094ae7f170993016 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 14:59:12 +0300 Subject: Dropdown: Remove static method used once --- js/src/dropdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 6129707e2..06f69af7b 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -132,7 +132,7 @@ class Dropdown extends BaseComponent { return } - const parent = Dropdown.getParentFromElement(this._element) + const parent = getElementFromSelector(this._element) || this._element.parentNode // Totally disable Popper for Dropdowns in Navbar if (this._inNavbar) { Manipulator.setDataAttribute(this._menu, 'popper', 'none') @@ -408,10 +408,6 @@ class Dropdown extends BaseComponent { } } - static getParentFromElement(element) { - return getElementFromSelector(element) || element.parentNode - } - static dataApiKeydownHandler(event) { // If not input/textarea: // - And not a key in REGEXP_KEYDOWN => not a dropdown command -- cgit v1.2.3 From fb5921dec49da37e9bab745d7319037e89e2f31e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 15:09:57 +0300 Subject: Dropdown: Merge `display='static'` & `isNavbar` functionality activating static popper with no styles attached --- js/src/dropdown.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 06f69af7b..d5c42b012 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -133,12 +133,8 @@ class Dropdown extends BaseComponent { } const parent = getElementFromSelector(this._element) || this._element.parentNode - // Totally disable Popper for Dropdowns in Navbar - if (this._inNavbar) { - Manipulator.setDataAttribute(this._menu, 'popper', 'none') - } else { - this._createPopper(parent) - } + + this._createPopper(parent) // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; @@ -246,13 +242,7 @@ class Dropdown extends BaseComponent { } const popperConfig = this._getPopperConfig() - const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false) - this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) - - if (isDisplayStatic) { - Manipulator.setDataAttribute(this._menu, 'popper', 'static') - } } _isShown(element = this._element) { @@ -319,8 +309,9 @@ class Dropdown extends BaseComponent { }] } - // Disable Popper if we have a static display - if (this._config.display === 'static') { + // Disable Popper if we have a static display or Dropdown is in Navbar + if (this._inNavbar || this._config.display === 'static') { + Manipulator.setDataAttribute(this._menu, 'popper', 'static') // todo:v6 remove defaultBsPopperConfig.modifiers = [{ name: 'applyStyles', enabled: false -- cgit v1.2.3 From 2d32802f53660b0146e0f78d1c8cd1fb58c0233e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 15:24:07 +0300 Subject: Dropdown: Change constant to the way we use it --- js/src/dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d5c42b012..2376e74cd 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -53,10 +53,10 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' -const CLASS_NAME_NAVBAR = 'navbar' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' const SELECTOR_MENU = '.dropdown-menu' +const SELECTOR_NAVBAR = '.navbar' const SELECTOR_NAVBAR_NAV = '.navbar-nav' const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' @@ -275,7 +275,7 @@ class Dropdown extends BaseComponent { } _detectNavbar() { - return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null + return this._element.closest(SELECTOR_NAVBAR) !== null } _getOffset() { -- cgit v1.2.3 From bff95d55af1074d67738c5f83d69f7b8cff5a22a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 15:39:47 +0300 Subject: Dropdown: Remove redundant check since the `show` method already does it --- js/src/dropdown.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 2376e74cd..6c613efc6 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -437,10 +437,7 @@ class Dropdown extends BaseComponent { } if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { - if (!isActive) { - instance.show() - } - + instance.show() instance._selectMenuItem(event) return } -- cgit v1.2.3 From a14a552d83a8f5452f2ef53a6a85a91c8aafb5f7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 16:21:34 +0300 Subject: Dropdown: Deduplicate complex check --- js/src/dropdown.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 6c613efc6..90bc582b9 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -39,8 +39,6 @@ const ARROW_UP_KEY = 'ArrowUp' const ARROW_DOWN_KEY = 'ArrowDown' const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button -const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`) - const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` @@ -407,14 +405,23 @@ class Dropdown extends BaseComponent { // - If key is other than escape // - If key is not up or down => not a dropdown command // - If trigger inside the menu => not a dropdown command - if (/input|textarea/i.test(event.target.tagName) ? - event.key === SPACE_KEY || (event.key !== ESCAPE_KEY && - ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) || - event.target.closest(SELECTOR_MENU))) : - !REGEXP_KEYDOWN.test(event.key)) { + + const isInput = /input|textarea/i.test(event.target.tagName) + const eventKey = event.key + if (!isInput && ![ARROW_UP_KEY, ARROW_DOWN_KEY, ESCAPE_KEY].includes(eventKey)) { return } + if (isInput) { + if (eventKey === SPACE_KEY) { + return + } + + if (eventKey !== ESCAPE_KEY && (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(eventKey) || event.target.closest(SELECTOR_MENU))) { + return + } + } + const isActive = this.classList.contains(CLASS_NAME_SHOW) if (!isActive && event.key === ESCAPE_KEY) { -- cgit v1.2.3 From 0686fa00f04f5479753a32890dcae25b6c134e71 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 16:31:39 +0300 Subject: Dropdown: Remove redundant `Space` check --- js/src/dropdown.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 90bc582b9..c769ed504 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -33,7 +33,6 @@ const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const ESCAPE_KEY = 'Escape' -const SPACE_KEY = 'Space' const TAB_KEY = 'Tab' const ARROW_UP_KEY = 'ArrowUp' const ARROW_DOWN_KEY = 'ArrowDown' @@ -399,11 +398,10 @@ class Dropdown extends BaseComponent { static dataApiKeydownHandler(event) { // If not input/textarea: - // - And not a key in REGEXP_KEYDOWN => not a dropdown command + // - And not a key in UP | DOWN | ESCAPE => not a dropdown command // If input/textarea: - // - If space key => not a dropdown command - // - If key is other than escape - // - If key is not up or down => not a dropdown command + // - If key is other than ESCAPE + // - If key is not UP or DOWN => not a dropdown command // - If trigger inside the menu => not a dropdown command const isInput = /input|textarea/i.test(event.target.tagName) @@ -413,10 +411,7 @@ class Dropdown extends BaseComponent { } if (isInput) { - if (eventKey === SPACE_KEY) { - return - } - + // eslint-disable-next-line unicorn/no-lonely-if if (eventKey !== ESCAPE_KEY && (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(eventKey) || event.target.closest(SELECTOR_MENU))) { return } @@ -446,11 +441,6 @@ class Dropdown extends BaseComponent { if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { instance.show() instance._selectMenuItem(event) - return - } - - if (!isActive || event.key === SPACE_KEY) { - Dropdown.clearMenus() } } } -- cgit v1.2.3 From 21e5618ba7007aa11b8a7751f1bbc1a9465dbab1 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 16:39:47 +0300 Subject: Dropdown: rename vars --- js/src/dropdown.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index c769ed504..d5dfe2e20 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -405,21 +405,23 @@ class Dropdown extends BaseComponent { // - If trigger inside the menu => not a dropdown command const isInput = /input|textarea/i.test(event.target.tagName) - const eventKey = event.key - if (!isInput && ![ARROW_UP_KEY, ARROW_DOWN_KEY, ESCAPE_KEY].includes(eventKey)) { + const isEscapeEvent = event.key === ESCAPE_KEY + const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key) + + if (!isInput && !(isUpOrDownEvent || isEscapeEvent)) { return } if (isInput) { // eslint-disable-next-line unicorn/no-lonely-if - if (eventKey !== ESCAPE_KEY && (![ARROW_UP_KEY, ARROW_DOWN_KEY].includes(eventKey) || event.target.closest(SELECTOR_MENU))) { + if (!isEscapeEvent && (!isUpOrDownEvent || event.target.closest(SELECTOR_MENU))) { return } } const isActive = this.classList.contains(CLASS_NAME_SHOW) - if (!isActive && event.key === ESCAPE_KEY) { + if (!isActive && isEscapeEvent) { return } @@ -433,12 +435,12 @@ class Dropdown extends BaseComponent { const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] const instance = Dropdown.getOrCreateInstance(getToggleButton) - if (event.key === ESCAPE_KEY) { + if (isEscapeEvent) { instance.hide() return } - if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { + if (isUpOrDownEvent) { instance.show() instance._selectMenuItem(event) } -- cgit v1.2.3 From f71640f04844f921613efee90b4868871f96f701 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 16:47:02 +0300 Subject: Dropdown: Clean more --- js/src/dropdown.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d5dfe2e20..510fcf1a4 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -399,8 +399,7 @@ class Dropdown extends BaseComponent { static dataApiKeydownHandler(event) { // If not input/textarea: // - And not a key in UP | DOWN | ESCAPE => not a dropdown command - // If input/textarea: - // - If key is other than ESCAPE + // If input/textarea && If key is other than ESCAPE // - If key is not UP or DOWN => not a dropdown command // - If trigger inside the menu => not a dropdown command @@ -412,9 +411,9 @@ class Dropdown extends BaseComponent { return } - if (isInput) { + if (isInput && !isEscapeEvent) { // eslint-disable-next-line unicorn/no-lonely-if - if (!isEscapeEvent && (!isUpOrDownEvent || event.target.closest(SELECTOR_MENU))) { + if (!isUpOrDownEvent || event.target.closest(SELECTOR_MENU)) { return } } -- cgit v1.2.3 From dd07c1ff9ee7102f607fbc8b62222ba51a57e81a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 10 Oct 2021 17:56:34 +0300 Subject: Dropdown: clearMenus is always an event callback --- js/src/dropdown.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 510fcf1a4..6fa3ea37a 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -351,7 +351,7 @@ class Dropdown extends BaseComponent { } static clearMenus(event) { - if (event && (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY))) { + if (event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY)) { return } @@ -371,25 +371,23 @@ class Dropdown extends BaseComponent { relatedTarget: context._element } - if (event) { - const composedPath = event.composedPath() - const isMenuTarget = composedPath.includes(context._menu) - if ( - composedPath.includes(context._element) || - (context._config.autoClose === 'inside' && !isMenuTarget) || - (context._config.autoClose === 'outside' && isMenuTarget) - ) { - continue - } + const composedPath = event.composedPath() + const isMenuTarget = composedPath.includes(context._menu) + if ( + composedPath.includes(context._element) || + (context._config.autoClose === 'inside' && !isMenuTarget) || + (context._config.autoClose === 'outside' && isMenuTarget) + ) { + continue + } - // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu - if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) { - continue - } + // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if (context._menu.contains(event.target) && ((event.type === 'keyup' && event.key === TAB_KEY) || /input|select|option|textarea|form/i.test(event.target.tagName))) { + continue + } - if (event.type === 'click') { - relatedTarget.clickEvent = event - } + if (event.type === 'click') { + relatedTarget.clickEvent = event } context._completeHide(relatedTarget) -- cgit v1.2.3 From 53c77c020368dbd570bd2d29ffc0c3660a0f6ab3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 25 Nov 2021 20:33:42 +0200 Subject: Tooltip: refactor jQueryInterface --- js/src/popover.js | 12 +++++++----- js/src/tooltip.js | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 19c1e42a4..aea1b9702 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -94,13 +94,15 @@ class Popover extends Tooltip { return this.each(function () { const data = Popover.getOrCreateInstance(this, config) - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } + if (typeof config !== 'string') { + return + } - data[config]() + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index b09ab0d0c..b0963a002 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -637,13 +637,15 @@ class Tooltip extends BaseComponent { return this.each(function () { const data = Tooltip.getOrCreateInstance(this, config) - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } + if (typeof config !== 'string') { + return + } - data[config]() + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } } -- cgit v1.2.3 From 3baeb0a5c12eb1bcea9336c883e8788b9dad68f8 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 26 Nov 2021 02:15:24 +0200 Subject: Tooltip: merge `isAnimated` checks --- js/src/tooltip.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index b0963a002..a1e701981 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -271,8 +271,7 @@ class Tooltip extends BaseComponent { } } - const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) - this._queueCallback(complete, this.tip, isAnimated) + this._queueCallback(complete, this.tip, this._isAnimated()) } hide() { @@ -315,8 +314,7 @@ class Tooltip extends BaseComponent { this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_HOVER] = false - const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE) - this._queueCallback(complete, this.tip, isAnimated) + this._queueCallback(complete, this.tip, this._isAnimated()) this._hoverState = '' } @@ -355,7 +353,7 @@ class Tooltip extends BaseComponent { tip.setAttribute('id', tipId) - if (this._config.animation) { + if (this._isAnimated()) { tip.classList.add(CLASS_NAME_FADE) } @@ -409,6 +407,10 @@ class Tooltip extends BaseComponent { return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) } + _isAnimated() { + return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE)) + } + _getOffset() { const { offset } = this._config -- cgit v1.2.3 From 724663b3cdebf436649d250c5ab52cca8f0c9320 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 02:46:28 +0200 Subject: Tooltip: Remove `Data.set` usage for dynamically created tip This is not used any further, so we were just setting it. --- js/src/tooltip.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index a1e701981..3027b8f4c 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -16,7 +16,6 @@ import { typeCheckConfig } from './util/index' import { DefaultAllowlist } from './util/sanitizer' -import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import BaseComponent from './base-component' @@ -231,7 +230,6 @@ class Tooltip extends BaseComponent { this._element.setAttribute('aria-describedby', tip.getAttribute('id')) const { container } = this._config - Data.set(tip, this.constructor.DATA_KEY, this) if (!this._element.ownerDocument.documentElement.contains(this.tip)) { container.append(tip) -- cgit v1.2.3 From a20e4203fe951593e804254f8d0593a822dc5e50 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 03:06:15 +0200 Subject: Tooltip: Remove redundant `config.delay` check `config.delay` is always an object after initialization --- js/src/tooltip.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 3027b8f4c..ccec08dda 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -528,7 +528,7 @@ class Tooltip extends BaseComponent { context._hoverState = HOVER_STATE_SHOW - if (!context._config.delay || !context._config.delay.show) { + if (!context._config.delay.show) { context.show() return } @@ -557,7 +557,7 @@ class Tooltip extends BaseComponent { context._hoverState = HOVER_STATE_OUT - if (!context._config.delay || !context._config.delay.hide) { + if (!context._config.delay.hide) { context.hide() return } -- cgit v1.2.3 From c69ccba08cd4f16d42bd83ffd40d2a57c9ca0ffc Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 03:09:42 +0200 Subject: Tooltip: Change `_enter` & `_leave` to work without arguments --- js/src/tooltip.js | 86 ++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 46 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index ccec08dda..645a6b7e2 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -179,17 +179,17 @@ class Tooltip extends BaseComponent { context._activeTrigger.click = !context._activeTrigger.click if (context._isWithActiveTrigger()) { - context._enter(null, context) + context._enter() } else { - context._leave(null, context) + context._leave() } } else { if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) { - this._leave(null, this) + this._leave() return } - this._enter(null, this) + this._enter() } } @@ -265,7 +265,7 @@ class Tooltip extends BaseComponent { EventHandler.trigger(this._element, this.constructor.Event.SHOWN) if (prevHoverState === HOVER_STATE_OUT) { - this._leave(null, this) + this._leave() } } @@ -401,8 +401,8 @@ class Tooltip extends BaseComponent { } // Private - _initializeOnDelegatedTarget(event, context) { - return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) + _initializeOnDelegatedTarget(event) { + return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) } _isAnimated() { @@ -478,8 +478,18 @@ class Tooltip extends BaseComponent { this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT - EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)) - EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)) + EventHandler.on(this._element, eventIn, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event) + context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true + context._enter() + }) + EventHandler.on(this._element, eventOut, this._config.selector, event => { + const context = this._initializeOnDelegatedTarget(event) + context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = + context._element.contains(event.relatedTarget) + + context._leave() + }) } } @@ -510,63 +520,47 @@ class Tooltip extends BaseComponent { } } - _enter(event, context) { - context = this._initializeOnDelegatedTarget(event, context) - - if (event) { - context._activeTrigger[ - event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER - ] = true - } - - if (context.getTipElement().classList.contains(CLASS_NAME_SHOW) || context._hoverState === HOVER_STATE_SHOW) { - context._hoverState = HOVER_STATE_SHOW + _enter() { + if (this.getTipElement().classList.contains(CLASS_NAME_SHOW) || this._hoverState === HOVER_STATE_SHOW) { + this._hoverState = HOVER_STATE_SHOW return } - clearTimeout(context._timeout) + clearTimeout(this._timeout) - context._hoverState = HOVER_STATE_SHOW + this._hoverState = HOVER_STATE_SHOW - if (!context._config.delay.show) { - context.show() + if (!this._config.delay.show) { + this.show() return } - context._timeout = setTimeout(() => { - if (context._hoverState === HOVER_STATE_SHOW) { - context.show() + this._timeout = setTimeout(() => { + if (this._hoverState === HOVER_STATE_SHOW) { + this.show() } - }, context._config.delay.show) + }, this._config.delay.show) } - _leave(event, context) { - context = this._initializeOnDelegatedTarget(event, context) - - if (event) { - context._activeTrigger[ - event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER - ] = context._element.contains(event.relatedTarget) - } - - if (context._isWithActiveTrigger()) { + _leave() { + if (this._isWithActiveTrigger()) { return } - clearTimeout(context._timeout) + clearTimeout(this._timeout) - context._hoverState = HOVER_STATE_OUT + this._hoverState = HOVER_STATE_OUT - if (!context._config.delay.hide) { - context.hide() + if (!this._config.delay.hide) { + this.hide() return } - context._timeout = setTimeout(() => { - if (context._hoverState === HOVER_STATE_OUT) { - context.hide() + this._timeout = setTimeout(() => { + if (this._hoverState === HOVER_STATE_OUT) { + this.hide() } - }, context._config.delay.hide) + }, this._config.delay.hide) } _isWithActiveTrigger() { -- cgit v1.2.3 From 8eacbaa08b1b8a5a935fd31368388f3f7e9e25da Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 03:58:59 +0200 Subject: Tooltip: merge timeout functionality --- js/src/tooltip.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 645a6b7e2..35abd2944 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -526,16 +526,9 @@ class Tooltip extends BaseComponent { return } - clearTimeout(this._timeout) - this._hoverState = HOVER_STATE_SHOW - if (!this._config.delay.show) { - this.show() - return - } - - this._timeout = setTimeout(() => { + this._setTimeout(() => { if (this._hoverState === HOVER_STATE_SHOW) { this.show() } @@ -547,22 +540,20 @@ class Tooltip extends BaseComponent { return } - clearTimeout(this._timeout) - this._hoverState = HOVER_STATE_OUT - if (!this._config.delay.hide) { - this.hide() - return - } - - this._timeout = setTimeout(() => { + this._setTimeout(() => { if (this._hoverState === HOVER_STATE_OUT) { this.hide() } }, this._config.delay.hide) } + _setTimeout(handler, timeout) { + clearTimeout(this._timeout) + this._timeout = setTimeout(handler, timeout) + } + _isWithActiveTrigger() { return Object.values(this._activeTrigger).includes(true) } -- cgit v1.2.3 From 9b9372e8ddd60413f3d9a582bd5481586d119d8d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 04:02:10 +0200 Subject: Tooltip: refactor `_hoverState` to Boolean to achieve better control --- js/src/tooltip.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 35abd2944..f03f9ef4d 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -34,9 +34,6 @@ const CLASS_NAME_FADE = 'fade' const CLASS_NAME_MODAL = 'modal' const CLASS_NAME_SHOW = 'show' -const HOVER_STATE_SHOW = 'show' -const HOVER_STATE_OUT = 'out' - const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` @@ -126,7 +123,7 @@ class Tooltip extends BaseComponent { // Private this._isEnabled = true this._timeout = 0 - this._hoverState = '' + this._isHovered = false this._activeTrigger = {} this._popper = null this._templateFactory = null @@ -259,12 +256,12 @@ class Tooltip extends BaseComponent { } const complete = () => { - const prevHoverState = this._hoverState + const prevHoverState = this._isHovered - this._hoverState = null + this._isHovered = false EventHandler.trigger(this._element, this.constructor.Event.SHOWN) - if (prevHoverState === HOVER_STATE_OUT) { + if (prevHoverState) { this._leave() } } @@ -283,7 +280,7 @@ class Tooltip extends BaseComponent { return } - if (this._hoverState !== HOVER_STATE_SHOW) { + if (!this._isHovered) { tip.remove() } @@ -313,7 +310,7 @@ class Tooltip extends BaseComponent { this._activeTrigger[TRIGGER_HOVER] = false this._queueCallback(complete, this.tip, this._isAnimated()) - this._hoverState = '' + this._isHovered = false } update() { @@ -521,15 +518,15 @@ class Tooltip extends BaseComponent { } _enter() { - if (this.getTipElement().classList.contains(CLASS_NAME_SHOW) || this._hoverState === HOVER_STATE_SHOW) { - this._hoverState = HOVER_STATE_SHOW + if (this.getTipElement().classList.contains(CLASS_NAME_SHOW) || this._isHovered) { + this._isHovered = true return } - this._hoverState = HOVER_STATE_SHOW + this._isHovered = true this._setTimeout(() => { - if (this._hoverState === HOVER_STATE_SHOW) { + if (this._isHovered) { this.show() } }, this._config.delay.show) @@ -540,10 +537,10 @@ class Tooltip extends BaseComponent { return } - this._hoverState = HOVER_STATE_OUT + this._isHovered = false this._setTimeout(() => { - if (this._hoverState === HOVER_STATE_OUT) { + if (!this._isHovered) { this.hide() } }, this._config.delay.hide) -- cgit v1.2.3 From 1f7b83203d893ba674f6a0c54481e84378276a19 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 28 Nov 2021 14:18:59 +0200 Subject: Tooltip: simplify popper check --- js/src/tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index f03f9ef4d..fc96812b4 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -314,7 +314,7 @@ class Tooltip extends BaseComponent { } update() { - if (this._popper !== null) { + if (this._popper) { this._popper.update() } } -- cgit v1.2.3 From bd79d69a73a77172584baadf9aa36e2d2091160e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 29 Nov 2021 16:16:42 +0200 Subject: Tooltip: a simple code-block position change --- js/src/tooltip.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index fc96812b4..5fd1927c9 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -274,27 +274,12 @@ class Tooltip extends BaseComponent { return } - const tip = this.getTipElement() - const complete = () => { - if (this._isWithActiveTrigger()) { - return - } - - if (!this._isHovered) { - tip.remove() - } - - this._element.removeAttribute('aria-describedby') - EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) - - this._disposePopper() - } - const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE) if (hideEvent.defaultPrevented) { return } + const tip = this.getTipElement() tip.classList.remove(CLASS_NAME_SHOW) // If this is a touch-enabled device we remove the extra @@ -309,6 +294,21 @@ class Tooltip extends BaseComponent { this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_HOVER] = false + const complete = () => { + if (this._isWithActiveTrigger()) { + return + } + + if (!this._isHovered) { + tip.remove() + } + + this._element.removeAttribute('aria-describedby') + EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) + + this._disposePopper() + } + this._queueCallback(complete, this.tip, this._isAnimated()) this._isHovered = false } -- cgit v1.2.3 From 385fea49e8a7c83f2e9655cd9453256aef254aaa Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 29 Nov 2021 16:25:12 +0200 Subject: Tooltip/Popover: add underscore prefix to protected functions --- js/src/popover.js | 6 +++--- js/src/tooltip.js | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index aea1b9702..77f847110 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -73,14 +73,14 @@ class Popover extends Tooltip { } // Overrides - isWithContent() { - return this.getTitle() || this._getContent() + _isWithContent() { + return this._getTitle() || this._getContent() } // Private _getContentForTemplate() { return { - [SELECTOR_TITLE]: this.getTitle(), + [SELECTOR_TITLE]: this._getTitle(), [SELECTOR_CONTENT]: this._getContent() } } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 5fd1927c9..2f3acda38 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -181,7 +181,7 @@ class Tooltip extends BaseComponent { context._leave() } } else { - if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) { + if (this._getTipElement().classList.contains(CLASS_NAME_SHOW)) { this._leave() return } @@ -208,7 +208,7 @@ class Tooltip extends BaseComponent { throw new Error('Please use show on visible elements') } - if (!(this.isWithContent() && this._isEnabled)) { + if (!(this._isWithContent() && this._isEnabled)) { return } @@ -222,7 +222,7 @@ class Tooltip extends BaseComponent { return } - const tip = this.getTipElement() + const tip = this._getTipElement() this._element.setAttribute('aria-describedby', tip.getAttribute('id')) @@ -279,7 +279,7 @@ class Tooltip extends BaseComponent { return } - const tip = this.getTipElement() + const tip = this._getTipElement() tip.classList.remove(CLASS_NAME_SHOW) // If this is a touch-enabled device we remove the extra @@ -320,11 +320,11 @@ class Tooltip extends BaseComponent { } // Protected - isWithContent() { - return Boolean(this.getTitle()) + _isWithContent() { + return Boolean(this._getTitle()) } - getTipElement() { + _getTipElement() { if (!this.tip) { this.tip = this._createTipElement(this._getContentForTemplate()) } @@ -389,11 +389,11 @@ class Tooltip extends BaseComponent { _getContentForTemplate() { return { - [SELECTOR_TOOLTIP_INNER]: this.getTitle() + [SELECTOR_TOOLTIP_INNER]: this._getTitle() } } - getTitle() { + _getTitle() { return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('title') } @@ -518,7 +518,7 @@ class Tooltip extends BaseComponent { } _enter() { - if (this.getTipElement().classList.contains(CLASS_NAME_SHOW) || this._isHovered) { + if (this._getTipElement().classList.contains(CLASS_NAME_SHOW) || this._isHovered) { this._isHovered = true return } -- cgit v1.2.3 From 328f723008cc39292a9f355e2eafb0fd04740656 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 7 Dec 2021 15:51:56 +0200 Subject: Tooltip: remove title attribute before show & add tests (#35456) --- js/src/tooltip.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 2f3acda38..19a9b3168 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -394,7 +394,7 @@ class Tooltip extends BaseComponent { } _getTitle() { - return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('title') + return this._config.title } // Private @@ -510,11 +510,17 @@ class Tooltip extends BaseComponent { } _fixTitle() { - const title = this._element.getAttribute('title') + const title = this._config.originalTitle - if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { + if (!title) { + return + } + + if (!this._element.getAttribute('aria-label') && !this._element.textContent) { this._element.setAttribute('aria-label', title) } + + this._element.removeAttribute('title') } _enter() { @@ -579,6 +585,8 @@ class Tooltip extends BaseComponent { } } + config.originalTitle = this._element.getAttribute('title') || '' + config.title = this._resolvePossibleFunction(config.title) || config.originalTitle if (typeof config.title === 'number') { config.title = config.title.toString() } -- cgit v1.2.3 From 4fd5539c75515527cb1335c31bbaf76209aab296 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 9 Dec 2021 15:05:50 +0200 Subject: ScrollBar.js. Minor refactoring and add test (#35492) --- js/src/util/scrollbar.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 55b7244ab..187a6694d 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -15,6 +15,8 @@ import { isElement } from './index' const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' const SELECTOR_STICKY_CONTENT = '.sticky-top' +const PROPERTY_PADDING = 'paddingRight' +const PROPERTY_MARGIN = 'marginRight' /** * Class definition @@ -36,17 +38,17 @@ class ScrollBarHelper { const width = this.getWidth() this._disableOverFlow() // give padding to element to balance the hidden scrollbar width - this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width) + this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width) // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth - this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width) - this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width) + this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width) + this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width) } reset() { this._resetElementAttributes(this._element, 'overflow') - this._resetElementAttributes(this._element, 'paddingRight') - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight') - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight') + this._resetElementAttributes(this._element, PROPERTY_PADDING) + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING) + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN) } isOverflowing() { @@ -86,10 +88,11 @@ class ScrollBarHelper { const value = Manipulator.getDataAttribute(element, styleProp) if (typeof value === 'undefined') { element.style.removeProperty(styleProp) - } else { - Manipulator.removeDataAttribute(element, styleProp) - element.style[styleProp] = value + return } + + Manipulator.removeDataAttribute(element, styleProp) + element.style[styleProp] = value } this._applyManipulationCallback(selector, manipulationCallBack) @@ -98,10 +101,11 @@ class ScrollBarHelper { _applyManipulationCallback(selector, callBack) { if (isElement(selector)) { callBack(selector) - } else { - for (const sel of SelectorEngine.find(selector, this._element)) { - callBack(sel) - } + return + } + + for (const sel of SelectorEngine.find(selector, this._element)) { + callBack(sel) } } } -- cgit v1.2.3 From c376cb07630f49ae2bbb464925afb3a2dbc5565e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 9 Dec 2021 15:34:17 +0200 Subject: Dropdown: fix toggle focus after dropdown is hidden using the `ESC` button (#35500) --- js/src/dropdown.js | 1 + 1 file changed, 1 insertion(+) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 6fa3ea37a..c4e7baf29 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -434,6 +434,7 @@ class Dropdown extends BaseComponent { if (isEscapeEvent) { instance.hide() + getToggleButton.focus() return } -- cgit v1.2.3 From 28a5a72ed56df3cc5efb1d5164376c9c9541e4f0 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 9 Dec 2021 15:49:28 +0200 Subject: Scrollbar - remove margin/padding properties properly (#35388) Co-authored-by: XhmikosR --- js/src/util/scrollbar.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 187a6694d..b81d4b237 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -15,8 +15,8 @@ import { isElement } from './index' const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top' const SELECTOR_STICKY_CONTENT = '.sticky-top' -const PROPERTY_PADDING = 'paddingRight' -const PROPERTY_MARGIN = 'marginRight' +const PROPERTY_PADDING = 'padding-right' +const PROPERTY_MARGIN = 'margin-right' /** * Class definition @@ -69,15 +69,15 @@ class ScrollBarHelper { } this._saveInitialAttribute(element, styleProp) - const calculatedValue = window.getComputedStyle(element)[styleProp] - element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px` + const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProp) + element.style.setProperty(styleProp, `${callback(Number.parseFloat(calculatedValue))}px`) } this._applyManipulationCallback(selector, manipulationCallBack) } _saveInitialAttribute(element, styleProp) { - const actualValue = element.style[styleProp] + const actualValue = element.style.getPropertyValue(styleProp) if (actualValue) { Manipulator.setDataAttribute(element, styleProp, actualValue) } @@ -86,13 +86,14 @@ class ScrollBarHelper { _resetElementAttributes(selector, styleProp) { const manipulationCallBack = element => { const value = Manipulator.getDataAttribute(element, styleProp) - if (typeof value === 'undefined') { + // We only want to remove the property if the value is `null`; the value can also be zero + if (value === null) { element.style.removeProperty(styleProp) return } Manipulator.removeDataAttribute(element, styleProp) - element.style[styleProp] = value + element.style.setProperty(styleProp, value) } this._applyManipulationCallback(selector, manipulationCallBack) -- cgit v1.2.3 From eaa801c89975144ceb80f7a65b9c0f741e6ae96c Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 10 Dec 2021 07:42:08 +0200 Subject: Toast: join multiple classList calls (#35507) --- js/src/toast.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/toast.js b/js/src/toast.js index c45721c8f..ba376d05e 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -5,11 +5,7 @@ * -------------------------------------------------------------------------- */ -import { - defineJQueryPlugin, - reflow, - typeCheckConfig -} from './util/index' +import { defineJQueryPlugin, reflow, typeCheckConfig } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import BaseComponent from './base-component' @@ -100,8 +96,7 @@ class Toast extends BaseComponent { this._element.classList.remove(CLASS_NAME_HIDE) // @deprecated reflow(this._element) - this._element.classList.add(CLASS_NAME_SHOW) - this._element.classList.add(CLASS_NAME_SHOWING) + this._element.classList.add(CLASS_NAME_SHOW, CLASS_NAME_SHOWING) this._queueCallback(complete, this._element, this._config.animation) } @@ -119,8 +114,7 @@ class Toast extends BaseComponent { const complete = () => { this._element.classList.add(CLASS_NAME_HIDE) // @deprecated - this._element.classList.remove(CLASS_NAME_SHOWING) - this._element.classList.remove(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOWING, CLASS_NAME_SHOW) EventHandler.trigger(this._element, EVENT_HIDDEN) } -- cgit v1.2.3 From 871c8bdd3fe7218c10aa18dacf0b5c612cfff82c Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 10 Dec 2021 07:48:04 +0200 Subject: util/index.js: minor refactoring (#35510) * rename variables * remove an unused variable * be more explicit * reuse variable --- js/src/util/index.js | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index 0ba6ce6f8..0407100d8 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -10,12 +10,12 @@ const MILLISECONDS_MULTIPLIER = 1000 const TRANSITION_END = 'transitionend' // Shoutout AngusCroll (https://goo.gl/pxwQGp) -const toType = obj => { - if (obj === null || obj === undefined) { - return `${obj}` +const toType = object => { + if (object === null || object === undefined) { + return `${object}` } - return Object.prototype.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() + return Object.prototype.toString.call(object).match(/\s([a-z]+)/i)[1].toLowerCase() } /** @@ -34,22 +34,22 @@ const getSelector = element => { let selector = element.getAttribute('data-bs-target') if (!selector || selector === '#') { - let hrefAttr = element.getAttribute('href') + let hrefAttribute = element.getAttribute('href') // The only valid content that could double as a selector are IDs or classes, // so everything starting with `#` or `.`. If a "real" URL is used as the selector, // `document.querySelector` will rightfully complain it is invalid. // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) { + if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { return null } // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { - hrefAttr = `#${hrefAttr.split('#')[1]}` + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}` } - selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null } return selector @@ -98,26 +98,26 @@ const triggerTransitionEnd = element => { element.dispatchEvent(new Event(TRANSITION_END)) } -const isElement = obj => { - if (!obj || typeof obj !== 'object') { +const isElement = object => { + if (!object || typeof object !== 'object') { return false } - if (typeof obj.jquery !== 'undefined') { - obj = obj[0] + if (typeof object.jquery !== 'undefined') { + object = object[0] } - return typeof obj.nodeType !== 'undefined' + return typeof object.nodeType !== 'undefined' } -const getElement = obj => { +const getElement = object => { // it's a jQuery object or a node element - if (isElement(obj)) { - return obj.jquery ? obj[0] : obj + if (isElement(object)) { + return object.jquery ? object[0] : object } - if (typeof obj === 'string' && obj.length > 0) { - return document.querySelector(obj) + if (typeof object === 'string' && object.length > 0) { + return document.querySelector(object) } return null @@ -199,10 +199,8 @@ const reflow = element => { } const getjQuery = () => { - const { jQuery } = window - - if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { - return jQuery + if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { + return window.jQuery } return null @@ -291,15 +289,15 @@ const executeAfterTransition = (callback, transitionElement, waitForTransition = * @return {Element|elem} The proper element */ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + const listLength = list.length let index = list.indexOf(activeElement) - // if the element does not exist in the list return an element depending on the direction and if cycle is allowed + // if the element does not exist in the list return an element + // depending on the direction and if cycle is allowed if (index === -1) { - return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0] + return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0] } - const listLength = list.length - index += shouldGetNext ? 1 : -1 if (isCycleAllowed) { -- cgit v1.2.3 From 63d38b19740eac25b8603dc9a9ae11e1df4667f7 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 10 Dec 2021 07:51:57 +0200 Subject: Tab: minor refactoring (#35511) * remove unneeded parentheses * move variable --- js/src/tab.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index 4a018ca77..f9969fb7a 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -5,12 +5,7 @@ * -------------------------------------------------------------------------- */ -import { - defineJQueryPlugin, - getElementFromSelector, - isDisabled, - reflow -} from './util/index' +import { defineJQueryPlugin, getElementFromSelector, isDisabled, reflow } from './util/index' import EventHandler from './dom/event-handler' import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' @@ -55,15 +50,15 @@ class Tab extends BaseComponent { // Public show() { - if ((this._element.parentNode && + if (this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && - this._element.classList.contains(CLASS_NAME_ACTIVE))) { + this._element.classList.contains(CLASS_NAME_ACTIVE)) { return } - let previous const target = getElementFromSelector(this._element) const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP) + let previous if (listElement) { const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE @@ -75,9 +70,7 @@ class Tab extends BaseComponent { EventHandler.trigger(previous, EVENT_HIDE, { relatedTarget: this._element }) : null - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { - relatedTarget: previous - }) + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget: previous }) if (showEvent.defaultPrevented || (hideEvent !== null && hideEvent.defaultPrevented)) { return -- cgit v1.2.3 From 886b940796b3595a03b44230ca8b78197c5ee1c5 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Dec 2021 18:18:18 +0200 Subject: Extract Component config functionality to a separate class (#33872) Co-authored-by: XhmikosR --- js/src/base-component.js | 26 ++++++++++------- js/src/carousel.js | 20 ++++--------- js/src/collapse.js | 19 +++++-------- js/src/dropdown.js | 14 ++------- js/src/modal.js | 27 ++++-------------- js/src/offcanvas.js | 29 +++++++------------ js/src/popover.js | 8 +++--- js/src/scrollspy.js | 19 +++++-------- js/src/toast.js | 25 ++++------------ js/src/tooltip.js | 19 +++++++------ js/src/util/backdrop.js | 27 ++++++++++++------ js/src/util/config.js | 63 +++++++++++++++++++++++++++++++++++++++++ js/src/util/focustrap.js | 27 +++++++++++------- js/src/util/index.js | 16 +---------- js/src/util/swipe.js | 28 +++++++++++------- js/src/util/template-factory.js | 31 ++++++++++---------- 16 files changed, 205 insertions(+), 193 deletions(-) create mode 100644 js/src/util/config.js (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 3c5eb460a..4140bf194 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -6,11 +6,9 @@ */ import Data from './dom/data' -import { - executeAfterTransition, - getElement -} from './util/index' +import { executeAfterTransition, getElement } from './util/index' import EventHandler from './dom/event-handler' +import Config from './util/config' /** * Constants @@ -22,15 +20,18 @@ const VERSION = '5.1.3' * Class definition */ -class BaseComponent { - constructor(element) { - element = getElement(element) +class BaseComponent extends Config { + constructor(element, config) { + super() + element = getElement(element) if (!element) { return } this._element = element + this._config = this._getConfig(config) + Data.set(this._element, this.constructor.DATA_KEY, this) } @@ -48,6 +49,13 @@ class BaseComponent { executeAfterTransition(callback, element, isAnimated) } + _getConfig(config) { + config = this._mergeConfigObj(config, this._element) + config = this._configAfterMerge(config) + this._typeCheckConfig(config) + return config + } + // Static static getInstance(element) { return Data.get(getElement(element), this.DATA_KEY) @@ -61,10 +69,6 @@ class BaseComponent { return VERSION } - static get NAME() { - throw new Error('You have to implement the static method "NAME" for each component!') - } - static get DATA_KEY() { return `bs.${this.NAME}` } diff --git a/js/src/carousel.js b/js/src/carousel.js index 3589f2206..e50894aa8 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -12,8 +12,7 @@ import { isRTL, isVisible, reflow, - triggerTransitionEnd, - typeCheckConfig + triggerTransitionEnd } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' @@ -95,7 +94,7 @@ const DefaultType = { class Carousel extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) this._items = null this._interval = null @@ -105,7 +104,6 @@ class Carousel extends BaseComponent { this.touchTimeout = null this._swipeHelper = null - this._config = this._getConfig(config) this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element) this._addEventListeners() } @@ -115,6 +113,10 @@ class Carousel extends BaseComponent { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -205,16 +207,6 @@ class Carousel extends BaseComponent { } // Private - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - } - typeCheckConfig(NAME, config, DefaultType) - return config - } - _addEventListeners() { if (this._config.keyboard) { EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)) diff --git a/js/src/collapse.js b/js/src/collapse.js index 642f7e840..56d4f51c2 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -10,11 +10,9 @@ import { getElement, getElementFromSelector, getSelectorFromElement, - reflow, - typeCheckConfig + reflow } from './util/index' import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' @@ -62,10 +60,9 @@ const DefaultType = { class Collapse extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) this._isTransitioning = false - this._config = this._getConfig(config) this._triggerArray = [] const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) @@ -96,6 +93,10 @@ class Collapse extends BaseComponent { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -210,15 +211,9 @@ class Collapse extends BaseComponent { } // Private - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...config - } + _configAfterMerge(config) { config.toggle = Boolean(config.toggle) // Coerce string values config.parent = getElement(config.parent) - typeCheckConfig(NAME, config, DefaultType) return config } diff --git a/js/src/dropdown.js b/js/src/dropdown.js index c4e7baf29..674150e01 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -15,8 +15,7 @@ import { isElement, isRTL, isVisible, - noop, - typeCheckConfig + noop } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' @@ -88,10 +87,9 @@ const DefaultType = { class Dropdown extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) this._popper = null - this._config = this._getConfig(config) this._menu = this._getMenuElement() this._inNavbar = this._detectNavbar() } @@ -205,13 +203,7 @@ class Dropdown extends BaseComponent { } _getConfig(config) { - config = { - ...this.constructor.Default, - ...Manipulator.getDataAttributes(this._element), - ...config - } - - typeCheckConfig(NAME, config, this.constructor.DefaultType) + config = super._getConfig(config) if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function' diff --git a/js/src/modal.js b/js/src/modal.js index b8b144774..569e6e590 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -5,16 +5,8 @@ * -------------------------------------------------------------------------- */ -import { - defineJQueryPlugin, - getElementFromSelector, - isRTL, - isVisible, - reflow, - typeCheckConfig -} from './util/index' +import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index' import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' @@ -70,9 +62,8 @@ const DefaultType = { class Modal extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) - this._config = this._getConfig(config) this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = this._initializeBackDrop() this._focustrap = this._initializeFocusTrap() @@ -86,6 +77,10 @@ class Modal extends BaseComponent { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -175,16 +170,6 @@ class Modal extends BaseComponent { }) } - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - } - typeCheckConfig(NAME, config, DefaultType) - return config - } - _showElement(relatedTarget) { // try to append dynamic modal if (!document.body.contains(this._element)) { diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 6878b1f62..acc0971fa 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -9,14 +9,12 @@ import { defineJQueryPlugin, getElementFromSelector, isDisabled, - isVisible, - typeCheckConfig + isVisible } from './util/index' import ScrollBarHelper from './util/scrollbar' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' import SelectorEngine from './dom/selector-engine' -import Manipulator from './dom/manipulator' import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap' import { enableDismissTrigger } from './util/component-functions' @@ -63,9 +61,8 @@ const DefaultType = { class Offcanvas extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) - this._config = this._getConfig(config) this._isShown = false this._backdrop = this._initializeBackDrop() this._focustrap = this._initializeFocusTrap() @@ -73,14 +70,18 @@ class Offcanvas extends BaseComponent { } // Getters - static get NAME() { - return NAME - } - static get Default() { return Default } + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + // Public toggle(relatedTarget) { return this._isShown ? this.hide() : this.show(relatedTarget) @@ -162,16 +163,6 @@ class Offcanvas extends BaseComponent { } // Private - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - } - typeCheckConfig(NAME, config, DefaultType) - return config - } - _initializeBackDrop() { return new Backdrop({ className: CLASS_NAME_BACKDROP, diff --git a/js/src/popover.js b/js/src/popover.js index 77f847110..375eb8b0a 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -60,6 +60,10 @@ class Popover extends Tooltip { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -68,10 +72,6 @@ class Popover extends Tooltip { return Event } - static get DefaultType() { - return DefaultType - } - // Overrides _isWithContent() { return this._getTitle() || this._getContent() diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 27bc0cd87..dc082a1b3 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -8,8 +8,7 @@ import { defineJQueryPlugin, getElement, - getSelectorFromElement, - typeCheckConfig + getSelectorFromElement } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' @@ -62,9 +61,8 @@ const DefaultType = { class ScrollSpy extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) this._scrollElement = this._element.tagName === 'BODY' ? window : this._element - this._config = this._getConfig(config) this._offsets = [] this._targets = [] this._activeTarget = null @@ -81,6 +79,10 @@ class ScrollSpy extends BaseComponent { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -135,17 +137,10 @@ class ScrollSpy extends BaseComponent { } // Private - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' && config ? config : {}) - } + _configAfterMerge(config) { config.target = getElement(config.target) || document.documentElement - typeCheckConfig(NAME, config, DefaultType) - return config } diff --git a/js/src/toast.js b/js/src/toast.js index ba376d05e..b85e20b60 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -5,9 +5,8 @@ * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, reflow, typeCheckConfig } from './util/index' +import { defineJQueryPlugin, reflow } from './util/index' import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' import BaseComponent from './base-component' import { enableDismissTrigger } from './util/component-functions' @@ -51,9 +50,8 @@ const Default = { class Toast extends BaseComponent { constructor(element, config) { - super(element) + super(element, config) - this._config = this._getConfig(config) this._timeout = null this._hasMouseInteraction = false this._hasKeyboardInteraction = false @@ -61,14 +59,14 @@ class Toast extends BaseComponent { } // Getters - static get DefaultType() { - return DefaultType - } - static get Default() { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -133,17 +131,6 @@ class Toast extends BaseComponent { } // Private - _getConfig(config) { - config = { - ...Default, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' && config ? config : {}) - } - - typeCheckConfig(NAME, config, this.constructor.DefaultType) - - return config - } _maybeScheduleHide() { if (!this._config.autohide) { diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 19a9b3168..9c8e54c66 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -12,8 +12,7 @@ import { getElement, getUID, isRTL, - noop, - typeCheckConfig + noop } from './util/index' import { DefaultAllowlist } from './util/sanitizer' import EventHandler from './dom/event-handler' @@ -140,6 +139,10 @@ class Tooltip extends BaseComponent { return Default } + static get DefaultType() { + return DefaultType + } + static get NAME() { return NAME } @@ -148,10 +151,6 @@ class Tooltip extends BaseComponent { return Event } - static get DefaultType() { - return DefaultType - } - // Public enable() { this._isEnabled = true @@ -571,11 +570,16 @@ class Tooltip extends BaseComponent { } config = { - ...this.constructor.Default, ...dataAttributes, ...(typeof config === 'object' && config ? config : {}) } + config = this._mergeConfigObj(config) + config = this._configAfterMerge(config) + this._typeCheckConfig(config) + return config + } + _configAfterMerge(config) { config.container = config.container === false ? document.body : getElement(config.container) if (typeof config.delay === 'number') { @@ -595,7 +599,6 @@ class Tooltip extends BaseComponent { config.content = config.content.toString() } - typeCheckConfig(NAME, config, this.constructor.DefaultType) return config } diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index fb1b2776b..63f2b581c 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -6,7 +6,8 @@ */ import EventHandler from '../dom/event-handler' -import { execute, executeAfterTransition, getElement, reflow, typeCheckConfig } from './index' +import { execute, executeAfterTransition, getElement, reflow } from './index' +import Config from './config' /** * Constants @@ -37,13 +38,27 @@ const DefaultType = { * Class definition */ -class Backdrop { +class Backdrop extends Config { constructor(config) { + super() this._config = this._getConfig(config) this._isAppended = false this._element = null } + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + // Public show(callback) { if (!this._config.isVisible) { @@ -104,15 +119,9 @@ class Backdrop { return this._element } - _getConfig(config) { - config = { - ...Default, - ...(typeof config === 'object' ? config : {}) - } - + _configAfterMerge(config) { // use getElement() with the default "body" to get a fresh Element on each instantiation config.rootElement = getElement(config.rootElement) - typeCheckConfig(NAME, config, DefaultType) return config } diff --git a/js/src/util/config.js b/js/src/util/config.js new file mode 100644 index 000000000..19d02955d --- /dev/null +++ b/js/src/util/config.js @@ -0,0 +1,63 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v5.1.3): util/config.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { isElement, toType } from './index' +import Manipulator from '../dom/manipulator' + +/** + * Class definition + */ + +class Config { + // Getters + static get Default() { + return {} + } + + static get DefaultType() { + return {} + } + + static get NAME() { + throw new Error('You have to implement the static method "NAME", for each component!') + } + + _getConfig(config) { + config = this._mergeConfigObj(config) + config = this._configAfterMerge(config) + this._typeCheckConfig(config) + return config + } + + _configAfterMerge(config) { + return config + } + + _mergeConfigObj(config, element) { + return { + ...this.constructor.Default, + ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}), + ...(typeof config === 'object' ? config : {}) + } + } + + _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { + for (const property of Object.keys(configTypes)) { + const expectedTypes = configTypes[property] + const value = config[property] + const valueType = isElement(value) ? 'element' : toType(value) + + if (!new RegExp(expectedTypes).test(valueType)) { + throw new TypeError( + `${this.constructor.NAME.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` + ) + } + } + } +} + +export default Config diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index a1975f489..46727ecf8 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -7,7 +7,7 @@ import EventHandler from '../dom/event-handler' import SelectorEngine from '../dom/selector-engine' -import { typeCheckConfig } from './index' +import Config from './config' /** * Constants @@ -37,13 +37,27 @@ const DefaultType = { * Class definition */ -class FocusTrap { +class FocusTrap extends Config { constructor(config) { + super() this._config = this._getConfig(config) this._isActive = false this._lastTabNavDirection = null } + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + // Public activate() { const { trapElement, autofocus } = this._config @@ -99,15 +113,6 @@ class FocusTrap { this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD } - - _getConfig(config) { - config = { - ...Default, - ...(typeof config === 'object' ? config : {}) - } - typeCheckConfig(NAME, config, DefaultType) - return config - } } export default FocusTrap diff --git a/js/src/util/index.js b/js/src/util/index.js index 0407100d8..8bd614d40 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -123,20 +123,6 @@ const getElement = object => { return null } -const typeCheckConfig = (componentName, config, configTypes) => { - for (const property of Object.keys(configTypes)) { - const expectedTypes = configTypes[property] - const value = config[property] - const valueType = value && isElement(value) ? 'element' : toType(value) - - if (!new RegExp(expectedTypes).test(valueType)) { - throw new TypeError( - `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` - ) - } - } -} - const isVisible = element => { if (!isElement(element) || element.getClientRects().length === 0) { return false @@ -327,5 +313,5 @@ export { onDOMContentLoaded, reflow, triggerTransitionEnd, - typeCheckConfig + toType } diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js index 87a5f7f5a..ac09b6fa1 100644 --- a/js/src/util/swipe.js +++ b/js/src/util/swipe.js @@ -5,8 +5,9 @@ * -------------------------------------------------------------------------- */ +import Config from './config' import EventHandler from '../dom/event-handler' -import { execute, typeCheckConfig } from './index' +import { execute } from './index' /** * Constants @@ -40,8 +41,9 @@ const DefaultType = { * Class definition */ -class Swipe { +class Swipe extends Config { constructor(element, config) { + super() this._element = element if (!element || !Swipe.isSupported()) { @@ -54,6 +56,19 @@ class Swipe { this._initEvents() } + // Getters + static get Default() { + return Default + } + + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + // Public dispose() { EventHandler.off(this._element, EVENT_KEY) @@ -118,15 +133,6 @@ class Swipe { } } - _getConfig(config) { - config = { - ...Default, - ...(typeof config === 'object' ? config : {}) - } - typeCheckConfig(NAME, config, DefaultType) - return config - } - _eventIsPointerPenTouch(event) { return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH) } diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index a9cee1086..8a8d4da79 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -6,8 +6,9 @@ */ import { DefaultAllowlist, sanitizeHtml } from './sanitizer' -import { getElement, isElement, typeCheckConfig } from '../util/index' +import { getElement, isElement } from '../util/index' import SelectorEngine from '../dom/selector-engine' +import Config from './config' /** * Constants @@ -44,20 +45,25 @@ const DefaultContentType = { * Class definition */ -class TemplateFactory { +class TemplateFactory extends Config { constructor(config) { + super() this._config = this._getConfig(config) } // Getters - static get NAME() { - return NAME - } - static get Default() { return Default } + static get DefaultType() { + return DefaultType + } + + static get NAME() { + return NAME + } + // Public getContent() { return Object.values(this._config.content) @@ -94,21 +100,14 @@ class TemplateFactory { } // Private - _getConfig(config) { - config = { - ...Default, - ...(typeof config === 'object' ? config : {}) - } - - typeCheckConfig(NAME, config, DefaultType) + _typeCheckConfig(config) { + super._typeCheckConfig(config) this._checkContent(config.content) - - return config } _checkContent(arg) { for (const [selector, content] of Object.entries(arg)) { - typeCheckConfig(NAME, { selector, entry: content }, DefaultContentType) + super._typeCheckConfig({ selector, entry: content }, DefaultContentType) } } -- cgit v1.2.3 From cd04fe015f9118930a86c678f034b5657878885a Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 15 Dec 2021 09:38:06 +0200 Subject: Scrollspy: minor refactoring (#35512) * reorder variables * join lines * use `filter(Boolean)` since it's clearer * use `for...of` --- js/src/scrollspy.js | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) (limited to 'js/src') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index dc082a1b3..029970ed2 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -5,11 +5,7 @@ * -------------------------------------------------------------------------- */ -import { - defineJQueryPlugin, - getElement, - getSelectorFromElement -} from './util/index' +import { defineJQueryPlugin, getElement, getSelectorFromElement } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' @@ -89,45 +85,34 @@ class ScrollSpy extends BaseComponent { // Public refresh() { - const autoMethod = this._scrollElement === this._scrollElement.window ? - METHOD_OFFSET : - METHOD_POSITION - - const offsetMethod = this._config.method === 'auto' ? - autoMethod : - this._config.method - - const offsetBase = offsetMethod === METHOD_POSITION ? - this._getScrollTop() : - 0 - this._offsets = [] this._targets = [] this._scrollHeight = this._getScrollHeight() + const autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION + const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method + const offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0 const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) .map(element => { const targetSelector = getSelectorFromElement(element) const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null - if (target) { - const targetBCR = target.getBoundingClientRect() - if (targetBCR.width || targetBCR.height) { - return [ - Manipulator[offsetMethod](target).top + offsetBase, - targetSelector - ] - } + if (!target) { + return null } - return null + const targetBCR = target.getBoundingClientRect() + + return targetBCR.width || targetBCR.height ? + [Manipulator[offsetMethod](target).top + offsetBase, targetSelector] : + null }) - .filter(item => item) + .filter(Boolean) .sort((a, b) => a[0] - b[0]) - for (const item of targets) { - this._offsets.push(item[0]) - this._targets.push(item[1]) + for (const target of targets) { + this._offsets.push(target[0]) + this._targets.push(target[1]) } } @@ -188,7 +173,7 @@ class ScrollSpy extends BaseComponent { return } - for (let i = this._offsets.length; i--;) { + for (const i of this._offsets.keys()) { const isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]) -- cgit v1.2.3 From d40fae456e0273c7e8c98cbdd17c55fd5d69ec47 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 15 Dec 2021 10:41:31 +0200 Subject: Popover.js: Accept empty content through `data-bs-content` (#35514) Co-authored-by: XhmikosR --- js/src/popover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/popover.js b/js/src/popover.js index 375eb8b0a..b62b6a212 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -34,7 +34,7 @@ const Default = { const DefaultType = { ...Tooltip.DefaultType, - content: '(string|element|function)' + content: '(null|string|element|function)' } const Event = { -- cgit v1.2.3 From e0960b08e030f8c4c0c838c1dd0c392209d51f92 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 15 Dec 2021 10:47:32 +0200 Subject: Tooltip: remove extraneous call to _getConfig() (#35540) BaseClass already initializes the config Co-authored-by: XhmikosR --- js/src/tooltip.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 9c8e54c66..94c3935bd 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -117,7 +117,7 @@ class Tooltip extends BaseComponent { throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)') } - super(element) + super(element, config) // Private this._isEnabled = true @@ -128,7 +128,6 @@ class Tooltip extends BaseComponent { this._templateFactory = null // Protected - this._config = this._getConfig(config) this.tip = null this._setListeners() -- cgit v1.2.3 From c2db7108c2ed45273fd4c9eb9155847153fe1512 Mon Sep 17 00:00:00 2001 From: "Phil E. Taylor" Date: Sat, 18 Dec 2021 05:58:31 +0000 Subject: Fix typo in comment (#35564) s/moddal/modal --- js/src/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 569e6e590..cc158d6ce 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -369,7 +369,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) }) - // avoid conflict when clicking moddal toggler while another one is open + // avoid conflict when clicking modal toggler while another one is open const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) if (allReadyOpen) { Modal.getInstance(allReadyOpen).hide() -- cgit v1.2.3 From 65cf77ae3ef676a5d9e2ea640393ec8055e8b953 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 21 Dec 2021 17:19:29 +0200 Subject: Popover/Tooltip: Fix vertical alignment on arrow of tip elements (#35527) Regression of #32692 Co-authored-by: XhmikosR --- js/src/tooltip.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 94c3935bd..aa54371e7 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -449,6 +449,16 @@ class Tooltip extends BaseComponent { options: { element: `.${this.constructor.NAME}-arrow` } + }, + { + name: 'preSetPlacement', + enabled: true, + phase: 'beforeMain', + fn: data => { + // Pre-set Popper's placement attribute in order to read the arrow sizes properly. + // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement + this._getTipElement().setAttribute('data-popper-placement', data.state.placement) + } } ] } @@ -624,7 +634,6 @@ class Tooltip extends BaseComponent { } // Static - static jQueryInterface(config) { return this.each(function () { const data = Tooltip.getOrCreateInstance(this, config) -- cgit v1.2.3 From d60f146507c94bd889f7049d77a4b3725a6fa0a9 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 8 Jul 2021 01:29:25 +0300 Subject: Carousel: add a helper to get the active element --- js/src/carousel.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index e50894aa8..14a0bd40b 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -173,7 +173,7 @@ class Carousel extends BaseComponent { } to(index) { - this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) + this._activeElement = this._getActive() const activeIndex = this._getItemIndex(this._activeElement) if (index > this._items.length - 1 || index < 0) { @@ -282,7 +282,7 @@ class Carousel extends BaseComponent { _triggerSlideEvent(relatedTarget, eventDirectionName) { const targetIndex = this._getItemIndex(relatedTarget) - const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)) + const fromIndex = this._getItemIndex(this._getActive()) return EventHandler.trigger(this._element, EVENT_SLIDE, { relatedTarget, @@ -312,7 +312,7 @@ class Carousel extends BaseComponent { } _updateInterval() { - const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) + const element = this._activeElement || this._getActive() if (!element) { return @@ -330,7 +330,7 @@ class Carousel extends BaseComponent { _slide(directionOrOrder, element) { const order = this._directionToOrder(directionOrOrder) - const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) + const activeElement = this._getActive() const activeElementIndex = this._getItemIndex(activeElement) const nextElement = element || this._getItemByOrder(order, activeElement) @@ -412,6 +412,10 @@ class Carousel extends BaseComponent { } } + _getActive() { + return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) + } + _directionToOrder(direction) { if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { return direction -- cgit v1.2.3 From 6f79721c82ecef5a4a25482e915ffa157965702c Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 02:02:44 +0300 Subject: Carousel: return early and drop a loop. We can achieve the same thing by querying the specific selector directly --- js/src/carousel.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 14a0bd40b..856d70dac 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -61,7 +61,6 @@ const SELECTOR_ITEM = '.carousel-item' const SELECTOR_ITEM_IMG = '.carousel-item img' const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev' const SELECTOR_INDICATORS = '.carousel-indicators' -const SELECTOR_INDICATOR = '[data-bs-target]' const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]' const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]' @@ -293,21 +292,20 @@ class Carousel extends BaseComponent { } _setActiveIndicatorElement(element) { - if (this._indicatorsElement) { - const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement) + if (!this._indicatorsElement) { + return + } - activeIndicator.classList.remove(CLASS_NAME_ACTIVE) - activeIndicator.removeAttribute('aria-current') + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement) - const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement) + activeIndicator.classList.remove(CLASS_NAME_ACTIVE) + activeIndicator.removeAttribute('aria-current') - for (const indicator of indicators) { - if (Number.parseInt(indicator.getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element)) { - indicator.classList.add(CLASS_NAME_ACTIVE) - indicator.setAttribute('aria-current', 'true') - break - } - } + const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${this._getItemIndex(element)}"]`, this._indicatorsElement) + + if (newActiveIndicator) { + newActiveIndicator.classList.add(CLASS_NAME_ACTIVE) + newActiveIndicator.setAttribute('aria-current', 'true') } } -- cgit v1.2.3 From ff4bf4a458d1bfab6a3cb8803e762fdb0de8bc3e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 02:11:14 +0300 Subject: Carousel: move carousel default interval to `_getConfig()` and simplify it --- js/src/carousel.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 856d70dac..336bcd261 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -161,7 +161,7 @@ class Carousel extends BaseComponent { this._interval = null } - if (this._config && this._config.interval && !this._isPaused) { + if (this._config.interval && !this._isPaused) { this._updateInterval() this._interval = setInterval( @@ -206,6 +206,11 @@ class Carousel extends BaseComponent { } // Private + _configAfterMerge(config) { + config.defaultInterval = config.interval + return config + } + _addEventListeners() { if (this._config.keyboard) { EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)) @@ -318,12 +323,7 @@ class Carousel extends BaseComponent { const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10) - if (elementInterval) { - this._config.defaultInterval = this._config.defaultInterval || this._config.interval - this._config.interval = elementInterval - } else { - this._config.interval = this._config.defaultInterval || this._config.interval - } + this._config.interval = elementInterval || this._config.defaultInterval } _slide(directionOrOrder, element) { -- cgit v1.2.3 From b8ee68cfa0f3516dc55aec5da6d7e43e2705f402 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 02:13:58 +0300 Subject: Carousel: remove always true `visibilityState` check According to https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState `visibilityState` is always a string, so the check was always true --- js/src/carousel.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 336bcd261..e91ba376c 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -126,6 +126,7 @@ class Carousel extends BaseComponent { } nextWhenVisible() { + // FIXME TODO use `document.visibilityState` // Don't call next when the page isn't visible // or the carousel or its parent isn't visible if (!document.hidden && isVisible(this._element)) { @@ -164,10 +165,7 @@ class Carousel extends BaseComponent { if (this._config.interval && !this._isPaused) { this._updateInterval() - this._interval = setInterval( - (document.visibilityState ? this.nextWhenVisible : this.next).bind(this), - this._config.interval - ) + this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval) } } -- cgit v1.2.3 From 0d4213bde39bb4f2e2bc5a0df699dad82780efa3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 02:17:28 +0300 Subject: Carousel: move repeated code to a method --- js/src/carousel.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index e91ba376c..51c5dded8 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -148,8 +148,7 @@ class Carousel extends BaseComponent { this.cycle(true) } - clearInterval(this._interval) - this._interval = null + this._clearInterval() } cycle(event) { @@ -157,11 +156,7 @@ class Carousel extends BaseComponent { this._isPaused = false } - if (this._interval) { - clearInterval(this._interval) - this._interval = null - } - + this._clearInterval() if (this._config.interval && !this._isPaused) { this._updateInterval() @@ -412,6 +407,13 @@ class Carousel extends BaseComponent { return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) } + _clearInterval() { + if (this._interval) { + clearInterval(this._interval) + this._interval = null + } + } + _directionToOrder(direction) { if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { return direction -- cgit v1.2.3 From 0d054bb0f1484dbc0c778d3643e96e37ff46d708 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Wed, 5 Jan 2022 17:20:15 +0000 Subject: Remove explicit use of aria-hidden for offcanvas when closed (#35589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove explicit use of aria-hidden & visibility for offcanvas when closed, handling it with css Co-authored-by: GeoSot Co-authored-by: Gaël Poupard --- js/src/offcanvas.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index acc0971fa..db6534039 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -31,6 +31,8 @@ const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const ESCAPE_KEY = 'Escape' const CLASS_NAME_SHOW = 'show' +const CLASS_NAME_SHOWING = 'showing' +const CLASS_NAME_HIDING = 'hiding' const CLASS_NAME_BACKDROP = 'offcanvas-backdrop' const OPEN_SELECTOR = '.offcanvas.show' @@ -99,24 +101,23 @@ class Offcanvas extends BaseComponent { } this._isShown = true - this._element.style.visibility = 'visible' - this._backdrop.show() if (!this._config.scroll) { new ScrollBarHelper().hide() } - this._element.removeAttribute('aria-hidden') this._element.setAttribute('aria-modal', true) this._element.setAttribute('role', 'dialog') - this._element.classList.add(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_SHOWING) const completeCallBack = () => { if (!this._config.scroll) { this._focustrap.activate() } + this._element.classList.add(CLASS_NAME_SHOW) + this._element.classList.remove(CLASS_NAME_SHOWING) EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget }) } @@ -137,14 +138,13 @@ class Offcanvas extends BaseComponent { this._focustrap.deactivate() this._element.blur() this._isShown = false - this._element.classList.remove(CLASS_NAME_SHOW) + this._element.classList.add(CLASS_NAME_HIDING) this._backdrop.hide() const completeCallback = () => { - this._element.setAttribute('aria-hidden', true) + this._element.classList.remove(CLASS_NAME_SHOW, CLASS_NAME_HIDING) this._element.removeAttribute('aria-modal') this._element.removeAttribute('role') - this._element.style.visibility = 'hidden' if (!this._config.scroll) { new ScrollBarHelper().reset() -- cgit v1.2.3 From 14c7dc1e886015f2ed845f0f8e88d3597694250f Mon Sep 17 00:00:00 2001 From: Ryan Berliner <22206986+RyanBerliner@users.noreply.github.com> Date: Thu, 13 Jan 2022 03:55:05 -0500 Subject: Fix: `isVisible` function behavior in case of a `
` element, on chrome 97 (#35682) --- js/src/util/index.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/util/index.js b/js/src/util/index.js index 8bd614d40..4e52fd3eb 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -128,7 +128,26 @@ const isVisible = element => { return false } - return getComputedStyle(element).getPropertyValue('visibility') === 'visible' + const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible' + // Handle `details` element as its content may falsie appear visible when it is closed + const closedDetails = element.closest('details:not([open])') + + if (!closedDetails) { + return elementIsVisible + } + + if (closedDetails !== element) { + const summary = element.closest('summary') + if (summary && summary.parentNode !== closedDetails) { + return false + } + + if (summary === null) { + return false + } + } + + return elementIsVisible } const isDisabled = element => { -- cgit v1.2.3 From 0c3dfe104b520d6ce466f265bf60c6d74785972e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sat, 9 Oct 2021 21:49:49 +0300 Subject: Remove a few unneeded variables --- js/src/dom/event-handler.js | 7 ++----- js/src/modal.js | 15 +++++++-------- js/src/util/focustrap.js | 9 +++------ 3 files changed, 12 insertions(+), 19 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index b9ebce324..64e52ed95 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -123,9 +123,7 @@ function bootstrapDelegationHandler(element, selector, fn) { } function findHandler(events, handler, delegationSelector = null) { - const uidEventList = Object.keys(events) - - for (const uidEvent of uidEventList) { + for (const uidEvent of Object.keys(events)) { const event = events[uidEvent] if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { @@ -140,9 +138,8 @@ function normalizeParams(originalTypeEvent, handler, delegationFn) { const delegation = typeof handler === 'string' const originalHandler = delegation ? delegationFn : handler let typeEvent = getTypeEvent(originalTypeEvent) - const isNative = nativeEvents.has(typeEvent) - if (!isNative) { + if (!nativeEvents.has(typeEvent)) { typeEvent = originalTypeEvent } diff --git a/js/src/modal.js b/js/src/modal.js index cc158d6ce..e06cf7516 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -279,23 +279,22 @@ class Modal extends BaseComponent { return } - const { classList, scrollHeight, style } = this._element - const isModalOverflowing = scrollHeight > document.documentElement.clientHeight - const initialOverflowY = style.overflowY + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight + const initialOverflowY = this._element.style.overflowY // return if the following background transition hasn't yet completed - if (initialOverflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC)) { + if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) { return } if (!isModalOverflowing) { - style.overflowY = 'hidden' + this._element.style.overflowY = 'hidden' } - classList.add(CLASS_NAME_STATIC) + this._element.classList.add(CLASS_NAME_STATIC) this._queueCallback(() => { - classList.remove(CLASS_NAME_STATIC) + this._element.classList.remove(CLASS_NAME_STATIC) this._queueCallback(() => { - style.overflowY = initialOverflowY + this._element.style.overflowY = initialOverflowY }, this._dialog) }, this._dialog) diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index 46727ecf8..88fd16b10 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -60,14 +60,12 @@ class FocusTrap extends Config { // Public activate() { - const { trapElement, autofocus } = this._config - if (this._isActive) { return } - if (autofocus) { - trapElement.focus() + if (this._config.autofocus) { + this._config.trapElement.focus() } EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop @@ -88,10 +86,9 @@ class FocusTrap extends Config { // Private _handleFocusin(event) { - const { target } = event const { trapElement } = this._config - if (target === document || target === trapElement || trapElement.contains(target)) { + if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) { return } -- cgit v1.2.3 From 3ac4451d47c41c3153d554eb7b84f558c137995e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sat, 9 Oct 2021 21:50:21 +0300 Subject: backdrop.js: cache `_getElement` calls --- js/src/util/backdrop.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 63f2b581c..8f121e5bd 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -68,11 +68,12 @@ class Backdrop extends Config { this._append() + const element = this._getElement() if (this._config.isAnimated) { - reflow(this._getElement()) + reflow(element) } - this._getElement().classList.add(CLASS_NAME_SHOW) + element.classList.add(CLASS_NAME_SHOW) this._emulateAnimation(() => { execute(callback) @@ -130,9 +131,10 @@ class Backdrop extends Config { return } - this._config.rootElement.append(this._getElement()) + const element = this._getElement() + this._config.rootElement.append(element) - EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { + EventHandler.on(element, EVENT_MOUSEDOWN, () => { execute(this._config.clickCallback) }) -- cgit v1.2.3 From 62d86c07f81dfae632742dbf62633e767bac8edd Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 29 Oct 2021 10:38:35 +0300 Subject: Rename variables --- js/src/carousel.js | 4 ++-- js/src/collapse.js | 16 ++++++++-------- js/src/dom/event-handler.js | 40 ++++++++++++++++++++-------------------- js/src/dom/manipulator.js | 14 +++++++------- js/src/dropdown.js | 12 ++++++------ js/src/modal.js | 6 +++--- js/src/offcanvas.js | 4 ++-- js/src/tooltip.js | 12 ++++++------ js/src/util/sanitizer.js | 6 +++--- js/src/util/scrollbar.js | 24 ++++++++++++------------ 10 files changed, 69 insertions(+), 69 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 51c5dded8..fe3ccf94e 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -220,8 +220,8 @@ class Carousel extends BaseComponent { } _addTouchEventListeners() { - for (const itemImg of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { - EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault()) + for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) { + EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault()) } const endCallBack = () => { diff --git a/js/src/collapse.js b/js/src/collapse.js index 56d4f51c2..68046e1a6 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -70,7 +70,7 @@ class Collapse extends BaseComponent { for (const elem of toggleList) { const selector = getSelectorFromElement(elem) const filterElement = SelectorEngine.find(selector) - .filter(foundElem => foundElem === this._element) + .filter(foundElement => foundElement === this._element) if (selector !== null && filterElement.length) { this._triggerArray.push(elem) @@ -185,9 +185,9 @@ class Collapse extends BaseComponent { this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW) for (const trigger of this._triggerArray) { - const elem = getElementFromSelector(trigger) + const element = getElementFromSelector(trigger) - if (elem && !this._isShown(elem)) { + if (element && !this._isShown(element)) { this._addAriaAndCollapsedClass([trigger], false) } } @@ -240,7 +240,7 @@ class Collapse extends BaseComponent { _getFirstLevelChildren(selector) { const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent) // remove children if greater depth - return SelectorEngine.find(selector, this._config.parent).filter(elem => !children.includes(elem)) + return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element)) } _addAriaAndCollapsedClass(triggerArray, isOpen) { @@ -248,14 +248,14 @@ class Collapse extends BaseComponent { return } - for (const elem of triggerArray) { + for (const element of triggerArray) { if (isOpen) { - elem.classList.remove(CLASS_NAME_COLLAPSED) + element.classList.remove(CLASS_NAME_COLLAPSED) } else { - elem.classList.add(CLASS_NAME_COLLAPSED) + element.classList.add(CLASS_NAME_COLLAPSED) } - elem.setAttribute('aria-expanded', isOpen) + element.setAttribute('aria-expanded', isOpen) } } diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 64e52ed95..09f2d4d8f 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -134,9 +134,9 @@ function findHandler(events, handler, delegationSelector = null) { return null } -function normalizeParams(originalTypeEvent, handler, delegationFn) { +function normalizeParameters(originalTypeEvent, handler, delegationFunction) { const delegation = typeof handler === 'string' - const originalHandler = delegation ? delegationFn : handler + const originalHandler = delegation ? delegationFunction : handler let typeEvent = getTypeEvent(originalTypeEvent) if (!nativeEvents.has(typeEvent)) { @@ -146,20 +146,20 @@ function normalizeParams(originalTypeEvent, handler, delegationFn) { return [delegation, originalHandler, typeEvent] } -function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { +function addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) { if (typeof originalTypeEvent !== 'string' || !element) { return } if (!handler) { - handler = delegationFn - delegationFn = null + handler = delegationFunction + delegationFunction = null } // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position // this prevents the handler from being dispatched the same way as mouseover or mouseout does if (customEventsRegex.test(originalTypeEvent)) { - const wrapFn = fn => { + const wrapFunction = fn => { return function (event) { if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))) { return fn.call(this, event) @@ -167,27 +167,27 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { } } - if (delegationFn) { - delegationFn = wrapFn(delegationFn) + if (delegationFunction) { + delegationFunction = wrapFunction(delegationFunction) } else { - handler = wrapFn(handler) + handler = wrapFunction(handler) } } - const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) + const [delegation, originalHandler, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) const events = getEvent(element) const handlers = events[typeEvent] || (events[typeEvent] = {}) - const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null) + const previousFunction = findHandler(handlers, originalHandler, delegation ? handler : null) - if (previousFn) { - previousFn.oneOff = previousFn.oneOff && oneOff + if (previousFunction) { + previousFunction.oneOff = previousFunction.oneOff && oneOff return } const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')) const fn = delegation ? - bootstrapDelegationHandler(element, handler, delegationFn) : + bootstrapDelegationHandler(element, handler, delegationFunction) : bootstrapHandler(element, handler) fn.delegationSelector = delegation ? handler : null @@ -228,20 +228,20 @@ function getTypeEvent(event) { } const EventHandler = { - on(element, event, handler, delegationFn) { - addHandler(element, event, handler, delegationFn, false) + on(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, false) }, - one(element, event, handler, delegationFn) { - addHandler(element, event, handler, delegationFn, true) + one(element, event, handler, delegationFunction) { + addHandler(element, event, handler, delegationFunction, true) }, - off(element, originalTypeEvent, handler, delegationFn) { + off(element, originalTypeEvent, handler, delegationFunction) { if (typeof originalTypeEvent !== 'string' || !element) { return } - const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) + const [delegation, originalHandler, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction) const inNamespace = typeEvent !== originalTypeEvent const events = getEvent(element) const isNamespace = originalTypeEvent.startsWith('.') diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index a3e9e192a..e3ee293c7 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -5,24 +5,24 @@ * -------------------------------------------------------------------------- */ -function normalizeData(val) { - if (val === 'true') { +function normalizeData(value) { + if (value === 'true') { return true } - if (val === 'false') { + if (value === 'false') { return false } - if (val === Number(val).toString()) { - return Number(val) + if (value === Number(value).toString()) { + return Number(value) } - if (val === '' || val === 'null') { + if (value === '' || value === 'null') { return null } - return val + return value } function normalizeDataKey(key) { diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 674150e01..9baa8d3a1 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -136,8 +136,8 @@ class Dropdown extends BaseComponent { // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) { - for (const elem of [].concat(...document.body.children)) { - EventHandler.on(elem, 'mouseover', noop) + for (const element of [].concat(...document.body.children)) { + EventHandler.on(element, 'mouseover', noop) } } @@ -186,8 +186,8 @@ class Dropdown extends BaseComponent { // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { - for (const elem of [].concat(...document.body.children)) { - EventHandler.off(elem, 'mouseover', noop) + for (const element of [].concat(...document.body.children)) { + EventHandler.off(element, 'mouseover', noop) } } @@ -271,7 +271,7 @@ class Dropdown extends BaseComponent { const { offset } = this._config if (typeof offset === 'string') { - return offset.split(',').map(val => Number.parseInt(val, 10)) + return offset.split(',').map(value => Number.parseInt(value, 10)) } if (typeof offset === 'function') { @@ -314,7 +314,7 @@ class Dropdown extends BaseComponent { } _selectMenuItem({ key, target }) { - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(el => isVisible(el)) + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)) if (!items.length) { return diff --git a/js/src/modal.js b/js/src/modal.js index e06cf7516..ae7369a52 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -369,9 +369,9 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) // avoid conflict when clicking modal toggler while another one is open - const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) - if (allReadyOpen) { - Modal.getInstance(allReadyOpen).hide() + const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR) + if (alreadyOpen) { + Modal.getInstance(alreadyOpen).hide() } const data = Modal.getOrCreateInstance(target) diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index db6534039..2735a9c2a 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -238,8 +238,8 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( }) EventHandler.on(window, EVENT_LOAD_DATA_API, () => { - for (const el of SelectorEngine.find(OPEN_SELECTOR)) { - Offcanvas.getOrCreateInstance(el).show() + for (const selector of SelectorEngine.find(OPEN_SELECTOR)) { + Offcanvas.getOrCreateInstance(selector).show() } }) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index aa54371e7..32f9cb91c 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -254,12 +254,12 @@ class Tooltip extends BaseComponent { } const complete = () => { - const prevHoverState = this._isHovered + const previousHoverState = this._isHovered this._isHovered = false EventHandler.trigger(this._element, this.constructor.Event.SHOWN) - if (prevHoverState) { + if (previousHoverState) { this._leave() } } @@ -408,7 +408,7 @@ class Tooltip extends BaseComponent { const { offset } = this._config if (typeof offset === 'string') { - return offset.split(',').map(val => Number.parseInt(val, 10)) + return offset.split(',').map(value => Number.parseInt(value, 10)) } if (typeof offset === 'function') { @@ -572,9 +572,9 @@ class Tooltip extends BaseComponent { _getConfig(config) { const dataAttributes = Manipulator.getDataAttributes(this._element) - for (const dataAttr of Object.keys(dataAttributes)) { - if (DISALLOWED_ATTRIBUTES.has(dataAttr)) { - delete dataAttributes[dataAttr] + for (const dataAttribute of Object.keys(dataAttributes)) { + if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) { + delete dataAttributes[dataAttribute] } } diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 5a7a68035..1db61ae70 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -82,13 +82,13 @@ export const DefaultAllowlist = { ul: [] } -export function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) { +export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!unsafeHtml.length) { return unsafeHtml } - if (sanitizeFn && typeof sanitizeFn === 'function') { - return sanitizeFn(unsafeHtml) + if (sanitizeFunction && typeof sanitizeFunction === 'function') { + return sanitizeFunction(unsafeHtml) } const domParser = new window.DOMParser() diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index b81d4b237..86a2bca01 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -61,39 +61,39 @@ class ScrollBarHelper { this._element.style.overflow = 'hidden' } - _setElementAttributes(selector, styleProp, callback) { + _setElementAttributes(selector, styleProperty, callback) { const scrollbarWidth = this.getWidth() const manipulationCallBack = element => { if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { return } - this._saveInitialAttribute(element, styleProp) - const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProp) - element.style.setProperty(styleProp, `${callback(Number.parseFloat(calculatedValue))}px`) + this._saveInitialAttribute(element, styleProperty) + const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty) + element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`) } this._applyManipulationCallback(selector, manipulationCallBack) } - _saveInitialAttribute(element, styleProp) { - const actualValue = element.style.getPropertyValue(styleProp) + _saveInitialAttribute(element, styleProperty) { + const actualValue = element.style.getPropertyValue(styleProperty) if (actualValue) { - Manipulator.setDataAttribute(element, styleProp, actualValue) + Manipulator.setDataAttribute(element, styleProperty, actualValue) } } - _resetElementAttributes(selector, styleProp) { + _resetElementAttributes(selector, styleProperty) { const manipulationCallBack = element => { - const value = Manipulator.getDataAttribute(element, styleProp) + const value = Manipulator.getDataAttribute(element, styleProperty) // We only want to remove the property if the value is `null`; the value can also be zero if (value === null) { - element.style.removeProperty(styleProp) + element.style.removeProperty(styleProperty) return } - Manipulator.removeDataAttribute(element, styleProp) - element.style.setProperty(styleProp, value) + Manipulator.removeDataAttribute(element, styleProperty) + element.style.setProperty(styleProperty, value) } this._applyManipulationCallback(selector, manipulationCallBack) -- cgit v1.2.3 From b5147ec218243f185ad2696f166e70c13641782e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Fri, 29 Oct 2021 10:38:52 +0300 Subject: event-handler.js: use `for...of` --- js/src/dom/event-handler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 09f2d4d8f..7c54a9765 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -104,8 +104,8 @@ function bootstrapDelegationHandler(element, selector, fn) { const domElements = element.querySelectorAll(selector) for (let { target } = event; target && target !== this; target = target.parentNode) { - for (let i = domElements.length; i--;) { - if (domElements[i] === target) { + for (const domElement of domElements) { + if (domElement === target) { event.delegateTarget = target if (handler.oneOff) { -- cgit v1.2.3 From a8887ea8a8996cd1b8cb49040305b2b7e5c6447d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 2 Nov 2021 16:40:34 +0200 Subject: collapse: merge class toggling --- js/src/collapse.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 68046e1a6..8894342df 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -249,12 +249,7 @@ class Collapse extends BaseComponent { } for (const element of triggerArray) { - if (isOpen) { - element.classList.remove(CLASS_NAME_COLLAPSED) - } else { - element.classList.add(CLASS_NAME_COLLAPSED) - } - + element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen) element.setAttribute('aria-expanded', isOpen) } } -- cgit v1.2.3 From 7d3bc44bb0257baff2728ed0ec97c81cb636bcf5 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 9 Nov 2021 15:43:02 +0200 Subject: dropdown: Move constant --- js/src/dropdown.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 9baa8d3a1..7f3b92655 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -359,10 +359,6 @@ class Dropdown extends BaseComponent { continue } - const relatedTarget = { - relatedTarget: context._element - } - const composedPath = event.composedPath() const isMenuTarget = composedPath.includes(context._menu) if ( @@ -378,6 +374,8 @@ class Dropdown extends BaseComponent { continue } + const relatedTarget = { relatedTarget: context._element } + if (event.type === 'click') { relatedTarget.clickEvent = event } -- cgit v1.2.3 From 558002f3dccb9fcb1ba408abfbe55d201af4e152 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Tue, 9 Nov 2021 15:44:14 +0200 Subject: Return early in more places --- js/src/carousel.js | 32 +++++++++++++++++--------------- js/src/dom/event-handler.js | 14 ++++++++------ 2 files changed, 25 insertions(+), 21 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index fe3ccf94e..5a0cbc208 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -225,22 +225,24 @@ class Carousel extends BaseComponent { } const endCallBack = () => { - if (this._config.pause === 'hover') { - // If it's a touch-enabled device, mouseenter/leave are fired as - // part of the mouse compatibility events on first tap - the carousel - // would stop cycling until user tapped out of it; - // here, we listen for touchend, explicitly pause the carousel - // (as if it's the second time we tap on it, mouseenter compat event - // is NOT fired) and after a timeout (to allow for mouse compatibility - // events to fire) we explicitly restart cycling - - this.pause() - if (this.touchTimeout) { - clearTimeout(this.touchTimeout) - } - - this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval) + if (this._config.pause !== 'hover') { + return } + + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + + this.pause() + if (this.touchTimeout) { + clearTimeout(this.touchTimeout) + } + + this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval) } const swipeConfig = { diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 7c54a9765..12b157467 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -105,15 +105,17 @@ function bootstrapDelegationHandler(element, selector, fn) { for (let { target } = event; target && target !== this; target = target.parentNode) { for (const domElement of domElements) { - if (domElement === target) { - event.delegateTarget = target + if (domElement !== target) { + continue + } - if (handler.oneOff) { - EventHandler.off(element, event.type, selector, fn) - } + event.delegateTarget = target - return fn.apply(target, [event]) + if (handler.oneOff) { + EventHandler.off(element, event.type, selector, fn) } + + return fn.apply(target, [event]) } } -- cgit v1.2.3 From 0840105d7f794c8f1c429eced58ffb80af453ff8 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Thu, 9 Dec 2021 21:52:39 +0200 Subject: SelectorEngine: remove moot space --- js/src/dom/selector-engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index af27dc379..39f3971dc 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -79,7 +79,7 @@ const SelectorEngine = { 'details', '[tabindex]', '[contenteditable="true"]' - ].map(selector => `${selector}:not([tabindex^="-"])`).join(', ') + ].map(selector => `${selector}:not([tabindex^="-"])`).join(',') return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)) } -- cgit v1.2.3 From fa939951232b7066722e928b370fd5e56b373fba Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 16 Dec 2021 13:23:17 +0200 Subject: Event handler: replace deprecated `initEvent` --- js/src/dom/event-handler.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 12b157467..f4305b618 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -289,7 +289,6 @@ const EventHandler = { let bubbles = true let nativeDispatch = true let defaultPrevented = false - let evt = null if (inNamespace && $) { jQueryEvent = $.Event(event, args) @@ -300,12 +299,9 @@ const EventHandler = { defaultPrevented = jQueryEvent.isDefaultPrevented() } - if (isNative) { - evt = document.createEvent('HTMLEvents') - evt.initEvent(typeEvent, bubbles, true) - } else { - evt = new CustomEvent(event, { bubbles, cancelable: true }) - } + const evt = isNative ? + new Event(event, { bubbles, cancelable: true }) : + new CustomEvent(event, { bubbles, cancelable: true }) // merge custom information in our event if (typeof args !== 'undefined') { -- cgit v1.2.3 From d09281705988690b63a5364548447c603cb557fd Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 16 Dec 2021 13:24:16 +0200 Subject: Event handler: merge `new Event` with `new CustomEvent` --- js/src/dom/event-handler.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index f4305b618..9ab1fe3c5 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -283,7 +283,6 @@ const EventHandler = { const $ = getjQuery() const typeEvent = getTypeEvent(event) const inNamespace = event !== typeEvent - const isNative = nativeEvents.has(typeEvent) let jQueryEvent let bubbles = true @@ -299,9 +298,7 @@ const EventHandler = { defaultPrevented = jQueryEvent.isDefaultPrevented() } - const evt = isNative ? - new Event(event, { bubbles, cancelable: true }) : - new CustomEvent(event, { bubbles, cancelable: true }) + const evt = new Event(event, { bubbles, cancelable: true }) // merge custom information in our event if (typeof args !== 'undefined') { -- cgit v1.2.3 From 28c9002573e6cb6982697e50e1d1ebfe2ae0fc4c Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 15 Dec 2021 19:54:02 +0200 Subject: Modal: handle click event from backdrop callback --- js/src/modal.js | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index ae7369a52..054750c5f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -30,7 +30,6 @@ const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const EVENT_RESIZE = `resize${EVENT_KEY}` -const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` @@ -115,7 +114,7 @@ class Modal extends BaseComponent { this._toggleEscapeEventListener(true) this._toggleResizeEventListener(true) - this._showBackdrop(() => this._showElement(relatedTarget)) + this._backdrop.show(() => this._showElement(relatedTarget)) } hide() { @@ -158,9 +157,22 @@ class Modal extends BaseComponent { // Private _initializeBackDrop() { + const clickCallback = () => { + if (this._config.backdrop === 'static') { + this._triggerBackdropTransition() + return + } + + this.hide() + } + + // 'static' option will be translated to true, and booleans will keep their value + const isVisible = Boolean(this._config.backdrop) + return new Backdrop({ - isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value - isAnimated: this._isAnimated() + isVisible, + isAnimated: this._isAnimated(), + clickCallback: isVisible ? clickCallback : null }) } @@ -250,25 +262,6 @@ class Modal extends BaseComponent { }) } - _showBackdrop(callback) { - EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { - if (event.target !== event.currentTarget) { - return - } - - if (this._config.backdrop === true) { - this.hide() - return - } - - if (this._config.backdrop === 'static') { - this._triggerBackdropTransition() - } - }) - - this._backdrop.show(callback) - } - _isAnimated() { return this._element.classList.contains(CLASS_NAME_FADE) } -- cgit v1.2.3 From 882185bbde9fa6ce0e7885404e76afa0090bdabb Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 30 Jan 2022 16:24:03 +0200 Subject: Change selector-engine.js `parents` method to utilize better js native methods (#35684) Co-authored-by: XhmikosR --- js/src/dom/selector-engine.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 39f3971dc..7f4165afc 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -11,8 +11,6 @@ import { isDisabled, isVisible } from '../util/index' * Constants */ -const NODE_TEXT = 3 - const SelectorEngine = { find(selector, element = document.documentElement) { return [].concat(...Element.prototype.querySelectorAll.call(element, selector)) @@ -28,14 +26,11 @@ const SelectorEngine = { parents(element, selector) { const parents = [] - let ancestor = element.parentNode - - while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT) { - if (ancestor.matches(selector)) { - parents.push(ancestor) - } + let ancestor = element.parentNode.closest(selector) - ancestor = ancestor.parentNode + while (ancestor) { + parents.push(ancestor) + ancestor = ancestor.parentNode.closest(selector) } return parents -- cgit v1.2.3 From 74f24cdf2458d917c7b7d8074706ec5823f4d166 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 30 Jan 2022 17:39:03 +0200 Subject: More tooltip refactoring (#35546) * Tooltip.js: move `shown` check to method * Tooltip.js: move Popper's creation to method * Tooltip.js: merge checks before `hide` * Tooltip.js: minor refactoring on `toggle` method --- js/src/tooltip.js | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 32f9cb91c..ef5b9fa82 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -178,14 +178,16 @@ class Tooltip extends BaseComponent { } else { context._leave() } - } else { - if (this._getTipElement().classList.contains(CLASS_NAME_SHOW)) { - this._leave() - return - } - this._enter() + return + } + + if (this._isShown()) { + this._leave() + return } + + this._enter() } dispose() { @@ -234,11 +236,7 @@ class Tooltip extends BaseComponent { if (this._popper) { this._popper.update() } else { - const placement = typeof this._config.placement === 'function' ? - this._config.placement.call(this, tip, this._element) : - this._config.placement - const attachment = AttachmentMap[placement.toUpperCase()] - this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) + this._createPopper(tip) } tip.classList.add(CLASS_NAME_SHOW) @@ -268,7 +266,7 @@ class Tooltip extends BaseComponent { } hide() { - if (!this._popper) { + if (!this._isShown()) { return } @@ -291,6 +289,7 @@ class Tooltip extends BaseComponent { this._activeTrigger[TRIGGER_CLICK] = false this._activeTrigger[TRIGGER_FOCUS] = false this._activeTrigger[TRIGGER_HOVER] = false + this._isHovered = false const complete = () => { if (this._isWithActiveTrigger()) { @@ -308,7 +307,6 @@ class Tooltip extends BaseComponent { } this._queueCallback(complete, this.tip, this._isAnimated()) - this._isHovered = false } update() { @@ -356,7 +354,7 @@ class Tooltip extends BaseComponent { setContent(content) { let isShown = false if (this.tip) { - isShown = this.tip.classList.contains(CLASS_NAME_SHOW) + isShown = this._isShown() this.tip.remove() this.tip = null } @@ -404,6 +402,18 @@ class Tooltip extends BaseComponent { return this._config.animation || (this.tip && this.tip.classList.contains(CLASS_NAME_FADE)) } + _isShown() { + return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW) + } + + _createPopper(tip) { + const placement = typeof this._config.placement === 'function' ? + this._config.placement.call(this, tip, this._element) : + this._config.placement + const attachment = AttachmentMap[placement.toUpperCase()] + this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) + } + _getOffset() { const { offset } = this._config @@ -532,7 +542,7 @@ class Tooltip extends BaseComponent { } _enter() { - if (this._getTipElement().classList.contains(CLASS_NAME_SHOW) || this._isHovered) { + if (this._isShown() || this._isHovered) { this._isHovered = true return } -- cgit v1.2.3 From 5f1c542d677add524c94054ba8583269d81d87d0 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 13 Dec 2021 02:10:26 +0200 Subject: Dropdown: get dropdown's parent in one place --- js/src/dom/selector-engine.js | 2 +- js/src/dropdown.js | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 7f4165afc..ed565bebb 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -49,7 +49,7 @@ const SelectorEngine = { return [] }, - + // TODO: this is now unused; remove later along with prev() next(element, selector) { let next = element.nextElementSibling diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 7f3b92655..efc3f2be3 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -90,7 +90,8 @@ class Dropdown extends BaseComponent { super(element, config) this._popper = null - this._menu = this._getMenuElement() + this._parent = getElementFromSelector(this._element) || this._element.parentNode // dropdown wrapper + this._menu = SelectorEngine.findOne(SELECTOR_MENU, this._parent) this._inNavbar = this._detectNavbar() } @@ -127,15 +128,13 @@ class Dropdown extends BaseComponent { return } - const parent = getElementFromSelector(this._element) || this._element.parentNode - - this._createPopper(parent) + this._createPopper() // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) { + if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) { for (const element of [].concat(...document.body.children)) { EventHandler.on(element, 'mouseover', noop) } @@ -215,7 +214,7 @@ class Dropdown extends BaseComponent { return config } - _createPopper(parent) { + _createPopper() { if (typeof Popper === 'undefined') { throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)') } @@ -223,7 +222,7 @@ class Dropdown extends BaseComponent { let referenceElement = this._element if (this._config.reference === 'parent') { - referenceElement = parent + referenceElement = this._parent } else if (isElement(this._config.reference)) { referenceElement = getElement(this._config.reference) } else if (typeof this._config.reference === 'object') { @@ -238,12 +237,8 @@ class Dropdown extends BaseComponent { return element.classList.contains(CLASS_NAME_SHOW) } - _getMenuElement() { - return SelectorEngine.next(this._element, SELECTOR_MENU)[0] - } - _getPlacement() { - const parentDropdown = this._element.parentNode + const parentDropdown = this._parent if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { return PLACEMENT_RIGHT -- cgit v1.2.3 From 7f04f84bf8562f2d8456649ee3cf78d181b52875 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 13 Dec 2021 02:17:03 +0200 Subject: Dropdown: use only one check for shown state --- js/src/dropdown.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index efc3f2be3..f63630409 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -114,7 +114,7 @@ class Dropdown extends BaseComponent { } show() { - if (isDisabled(this._element) || this._isShown(this._menu)) { + if (isDisabled(this._element) || this._isShown()) { return } @@ -149,7 +149,7 @@ class Dropdown extends BaseComponent { } hide() { - if (isDisabled(this._element) || !this._isShown(this._menu)) { + if (isDisabled(this._element) || !this._isShown()) { return } @@ -233,8 +233,8 @@ class Dropdown extends BaseComponent { this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) } - _isShown(element = this._element) { - return element.classList.contains(CLASS_NAME_SHOW) + _isShown() { + return this._menu.classList.contains(CLASS_NAME_SHOW) } _getPlacement() { -- cgit v1.2.3 From d10543923531d9a2bf0e122439d5b1a2ae4e3d13 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sun, 30 Jan 2022 23:50:22 +0200 Subject: Dropdown: merge instance identification in `dataApiKeydownHandler` As we use the `dataApiKeydownHandler` only for events that are triggered on `[data-bs-toggle="dropdown"]` or on `.dropdown-menu`, we can ensure that their `parentNode` will ALWAYS be the `.dropdown` wrapper --- js/src/dropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index f63630409..779fe8f19 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -414,7 +414,7 @@ class Dropdown extends BaseComponent { return } - const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] + const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode) const instance = Dropdown.getOrCreateInstance(getToggleButton) if (isEscapeEvent) { -- cgit v1.2.3 From c14fc989df8dc107b0ba86c241487b192841d753 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 31 Jan 2022 00:07:05 +0200 Subject: Dropdown: dropdown doesn't document `data-bs-target` option & `parentNode` is ALWAYS the wrapper for toggle & menu --- js/src/dropdown.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 779fe8f19..ed3feceb8 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -9,7 +9,6 @@ import * as Popper from '@popperjs/core' import { defineJQueryPlugin, getElement, - getElementFromSelector, getNextActiveElement, isDisabled, isElement, @@ -90,7 +89,7 @@ class Dropdown extends BaseComponent { super(element, config) this._popper = null - this._parent = getElementFromSelector(this._element) || this._element.parentNode // dropdown wrapper + this._parent = this._element.parentNode // dropdown wrapper this._menu = SelectorEngine.findOne(SELECTOR_MENU, this._parent) this._inNavbar = this._detectNavbar() } -- cgit v1.2.3 From c44d99f55c0e1dcc5a23a9f420972bfccfcddb13 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 31 Jan 2022 00:09:13 +0200 Subject: Dropdown: use destructured variables in `dataApyKeydownHandler` --- js/src/dropdown.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index ed3feceb8..5635ec96e 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -385,9 +385,10 @@ class Dropdown extends BaseComponent { // - If key is not UP or DOWN => not a dropdown command // - If trigger inside the menu => not a dropdown command - const isInput = /input|textarea/i.test(event.target.tagName) - const isEscapeEvent = event.key === ESCAPE_KEY - const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key) + const { target, key, delegateTarget } = event + const isInput = /input|textarea/i.test(target.tagName) + const isEscapeEvent = key === ESCAPE_KEY + const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(key) if (!isInput && !(isUpOrDownEvent || isEscapeEvent)) { return @@ -395,12 +396,12 @@ class Dropdown extends BaseComponent { if (isInput && !isEscapeEvent) { // eslint-disable-next-line unicorn/no-lonely-if - if (!isUpOrDownEvent || event.target.closest(SELECTOR_MENU)) { + if (!isUpOrDownEvent || target.closest(SELECTOR_MENU)) { return } } - const isActive = this.classList.contains(CLASS_NAME_SHOW) + const isActive = delegateTarget.classList.contains(CLASS_NAME_SHOW) if (!isActive && isEscapeEvent) { return @@ -413,7 +414,7 @@ class Dropdown extends BaseComponent { return } - const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode) + const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, delegateTarget.parentNode) const instance = Dropdown.getOrCreateInstance(getToggleButton) if (isEscapeEvent) { -- cgit v1.2.3 From fc7c5fcb7a5c6b9a27fd10398f6a4c52bf1c2a38 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 16 Dec 2021 13:25:23 +0200 Subject: Event-handler: initialize variable properly --- js/src/dom/event-handler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 9ab1fe3c5..49b75d4ad 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -284,7 +284,7 @@ const EventHandler = { const typeEvent = getTypeEvent(event) const inNamespace = event !== typeEvent - let jQueryEvent + let jQueryEvent = null let bubbles = true let nativeDispatch = true let defaultPrevented = false @@ -319,7 +319,7 @@ const EventHandler = { element.dispatchEvent(evt) } - if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { + if (evt.defaultPrevented && jQueryEvent) { jQueryEvent.preventDefault() } -- cgit v1.2.3 From a1e924c4da03fd05a1c50d7b278350c3cfcfe5c4 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 16 Dec 2021 13:43:36 +0200 Subject: Event-handler: use `Array.find` instead of `for` --- js/src/dom/event-handler.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 49b75d4ad..70d2f1708 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -125,15 +125,8 @@ function bootstrapDelegationHandler(element, selector, fn) { } function findHandler(events, handler, delegationSelector = null) { - for (const uidEvent of Object.keys(events)) { - const event = events[uidEvent] - - if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { - return event - } - } - - return null + return Object.values(events) + .find(event => event.originalHandler === handler && event.delegationSelector === delegationSelector) } function normalizeParameters(originalTypeEvent, handler, delegationFunction) { -- cgit v1.2.3 From cf7fec8a2e1cff03378c0802ff5cbe5ebdc5645a Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Thu, 18 Nov 2021 21:47:27 +0200 Subject: event-handler.js: remove unneeded return statement --- js/src/dom/event-handler.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 70d2f1708..a31ed333c 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -118,9 +118,6 @@ function bootstrapDelegationHandler(element, selector, fn) { return fn.apply(target, [event]) } } - - // To please ESLint - return null } } -- cgit v1.2.3 From a805330f63e092ba8f4583ccf6e16630ada8d86f Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 7 Feb 2022 11:50:26 +0300 Subject: Optimize jQueryInterface in Collapse (#35689) extracts config initialization from cycle --- js/src/collapse.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/collapse.js b/js/src/collapse.js index 8894342df..b1088e106 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -256,12 +256,12 @@ class Collapse extends BaseComponent { // Static static jQueryInterface(config) { - return this.each(function () { - const _config = {} - if (typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false - } + const _config = {} + if (typeof config === 'string' && /show|hide/.test(config)) { + _config.toggle = false + } + return this.each(function () { const data = Collapse.getOrCreateInstance(this, _config) if (typeof config === 'string') { -- cgit v1.2.3 From ccba6a3589ba33e25ab2919cb844b5bc870e2fef Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 02:55:28 +0300 Subject: Carousel: remove redundant config merge on `dataApiClickHandler`, as it is done by default in the `constructor` --- js/src/carousel.js | 1 - 1 file changed, 1 deletion(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 5a0cbc208..38e33cbee 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -482,7 +482,6 @@ class Carousel extends BaseComponent { } const config = { - ...Manipulator.getDataAttributes(target), ...Manipulator.getDataAttributes(this) } const slideIndex = this.getAttribute('data-bs-slide-to') -- cgit v1.2.3 From a247fe9b27ec57152f07aaa5dfc79f633d2b6d10 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 03:41:03 +0300 Subject: Carousel: simplify initialization on document load, using `getOrCreateInstance` --- js/src/carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 38e33cbee..4cb03d51f 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -510,7 +510,7 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) for (const carousel of carousels) { - Carousel.carouselInterface(carousel, Carousel.getInstance(carousel)) + Carousel.getOrCreateInstance(carousel) } }) -- cgit v1.2.3 From d97125475b3a5cbbafe3100d5981fd456c92c722 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 31 Jan 2022 00:22:33 +0200 Subject: Carousel: merge slide functionality, regardless of whether it is animated or not --- js/src/carousel.js | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 4cb03d51f..00d930495 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -372,39 +372,35 @@ class Carousel extends BaseComponent { }) } - if (this._element.classList.contains(CLASS_NAME_SLIDE)) { - nextElement.classList.add(orderClassName) + nextElement.classList.add(orderClassName) - reflow(nextElement) + reflow(nextElement) - activeElement.classList.add(directionalClassName) - nextElement.classList.add(directionalClassName) + activeElement.classList.add(directionalClassName) + nextElement.classList.add(directionalClassName) - const completeCallBack = () => { - nextElement.classList.remove(directionalClassName, orderClassName) - nextElement.classList.add(CLASS_NAME_ACTIVE) - - activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName) - - this._isSliding = false - - setTimeout(triggerSlidEvent, 0) - } - - this._queueCallback(completeCallBack, activeElement, true) - } else { - activeElement.classList.remove(CLASS_NAME_ACTIVE) + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName) nextElement.classList.add(CLASS_NAME_ACTIVE) + activeElement.classList.remove(CLASS_NAME_ACTIVE, orderClassName, directionalClassName) + this._isSliding = false - triggerSlidEvent() + + setTimeout(triggerSlidEvent, 0) } + this._queueCallback(completeCallBack, activeElement, this._isAnimated()) + if (isCycling) { this.cycle() } } + _isAnimated() { + return this._element.classList.contains(CLASS_NAME_SLIDE) + } + _getActive() { return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) } -- cgit v1.2.3 From 928bdcadc56cef30b4483616c4b2eee7cfa34bd2 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Mon, 31 Jan 2022 00:31:07 +0200 Subject: Carousel: make direct triggering of slid event, instead of using a callback --- js/src/carousel.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 00d930495..b8d921e42 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -363,15 +363,6 @@ class Carousel extends BaseComponent { this._setActiveIndicatorElement(nextElement) this._activeElement = nextElement - const triggerSlidEvent = () => { - EventHandler.trigger(this._element, EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }) - } - nextElement.classList.add(orderClassName) reflow(nextElement) @@ -387,7 +378,12 @@ class Carousel extends BaseComponent { this._isSliding = false - setTimeout(triggerSlidEvent, 0) + EventHandler.trigger(this._element, EVENT_SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex + }) } this._queueCallback(completeCallBack, activeElement, this._isAnimated()) -- cgit v1.2.3 From d52f6c9de144ac2bc3eba002d3db538183c52465 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 12:00:28 +0300 Subject: Carousel: change argument to `_setActiveIndicatorElement`, from element to index --- js/src/carousel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index b8d921e42..e3c836048 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -291,7 +291,7 @@ class Carousel extends BaseComponent { }) } - _setActiveIndicatorElement(element) { + _setActiveIndicatorElement(index) { if (!this._indicatorsElement) { return } @@ -301,7 +301,7 @@ class Carousel extends BaseComponent { activeIndicator.classList.remove(CLASS_NAME_ACTIVE) activeIndicator.removeAttribute('aria-current') - const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${this._getItemIndex(element)}"]`, this._indicatorsElement) + const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to="${index}"]`, this._indicatorsElement) if (newActiveIndicator) { newActiveIndicator.classList.add(CLASS_NAME_ACTIVE) @@ -360,7 +360,7 @@ class Carousel extends BaseComponent { this.pause() } - this._setActiveIndicatorElement(nextElement) + this._setActiveIndicatorElement(nextElementIndex) this._activeElement = nextElement nextElement.classList.add(orderClassName) -- cgit v1.2.3 From 642d756eeaf107bed2baac931933c9350c33f5c1 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 12:54:20 +0300 Subject: Carousel: remove one more call to ActiveIndex --- js/src/carousel.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index e3c836048..7b22e9cbf 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -279,9 +279,8 @@ class Carousel extends BaseComponent { return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) } - _triggerSlideEvent(relatedTarget, eventDirectionName) { + _triggerSlideEvent(relatedTarget, fromIndex, eventDirectionName) { const targetIndex = this._getItemIndex(relatedTarget) - const fromIndex = this._getItemIndex(this._getActive()) return EventHandler.trigger(this._element, EVENT_SLIDE, { relatedTarget, @@ -344,7 +343,7 @@ class Carousel extends BaseComponent { return } - const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName) + const slideEvent = this._triggerSlideEvent(nextElement, activeElementIndex, eventDirectionName) if (slideEvent.defaultPrevented) { return } -- cgit v1.2.3 From 407af8ac7f9296627aebc1e4c5d0ee948f8be1f3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 19 Feb 2022 15:10:47 +0200 Subject: Make event name helper and use it on tooltip & popover to reduce dist sizes (#35856) * feat: create eventName getter function in baseComponent * refactor: use `eventName` getter on tooltip & popover --- js/src/base-component.js | 4 ++++ js/src/popover.js | 19 ---------------- js/src/tooltip.js | 59 ++++++++++++++++++------------------------------ 3 files changed, 26 insertions(+), 56 deletions(-) (limited to 'js/src') diff --git a/js/src/base-component.js b/js/src/base-component.js index 4140bf194..75bb90c32 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -76,6 +76,10 @@ class BaseComponent extends Config { static get EVENT_KEY() { return `.${this.DATA_KEY}` } + + static eventName(name) { + return `${name}${this.EVENT_KEY}` + } } export default BaseComponent diff --git a/js/src/popover.js b/js/src/popover.js index b62b6a212..b6d1e2010 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -13,8 +13,6 @@ import Tooltip from './tooltip' */ const NAME = 'popover' -const DATA_KEY = 'bs.popover' -const EVENT_KEY = `.${DATA_KEY}` const SELECTOR_TITLE = '.popover-header' const SELECTOR_CONTENT = '.popover-body' @@ -37,19 +35,6 @@ const DefaultType = { content: '(null|string|element|function)' } -const Event = { - HIDE: `hide${EVENT_KEY}`, - HIDDEN: `hidden${EVENT_KEY}`, - SHOW: `show${EVENT_KEY}`, - SHOWN: `shown${EVENT_KEY}`, - INSERTED: `inserted${EVENT_KEY}`, - CLICK: `click${EVENT_KEY}`, - FOCUSIN: `focusin${EVENT_KEY}`, - FOCUSOUT: `focusout${EVENT_KEY}`, - MOUSEENTER: `mouseenter${EVENT_KEY}`, - MOUSELEAVE: `mouseleave${EVENT_KEY}` -} - /** * Class definition */ @@ -68,10 +53,6 @@ class Popover extends Tooltip { return NAME } - static get Event() { - return Event - } - // Overrides _isWithContent() { return this._getTitle() || this._getContent() diff --git a/js/src/tooltip.js b/js/src/tooltip.js index ef5b9fa82..5cf56ce6e 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -6,14 +6,7 @@ */ import * as Popper from '@popperjs/core' -import { - defineJQueryPlugin, - findShadowRoot, - getElement, - getUID, - isRTL, - noop -} from './util/index' +import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index' import { DefaultAllowlist } from './util/sanitizer' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' @@ -25,8 +18,6 @@ import TemplateFactory from './util/template-factory' */ const NAME = 'tooltip' -const DATA_KEY = 'bs.tooltip' -const EVENT_KEY = `.${DATA_KEY}` const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']) const CLASS_NAME_FADE = 'fade' @@ -43,6 +34,17 @@ const TRIGGER_FOCUS = 'focus' const TRIGGER_CLICK = 'click' const TRIGGER_MANUAL = 'manual' +const EVENT_HIDE = 'hide' +const EVENT_HIDDEN = 'hidden' +const EVENT_SHOW = 'show' +const EVENT_SHOWN = 'shown' +const EVENT_INSERTED = 'inserted' +const EVENT_CLICK = 'click' +const EVENT_FOCUSIN = 'focusin' +const EVENT_FOCUSOUT = 'focusout' +const EVENT_MOUSEENTER = 'mouseenter' +const EVENT_MOUSELEAVE = 'mouseleave' + const AttachmentMap = { AUTO: 'auto', TOP: 'top', @@ -94,19 +96,6 @@ const DefaultType = { popperConfig: '(null|object|function)' } -const Event = { - HIDE: `hide${EVENT_KEY}`, - HIDDEN: `hidden${EVENT_KEY}`, - SHOW: `show${EVENT_KEY}`, - SHOWN: `shown${EVENT_KEY}`, - INSERTED: `inserted${EVENT_KEY}`, - CLICK: `click${EVENT_KEY}`, - FOCUSIN: `focusin${EVENT_KEY}`, - FOCUSOUT: `focusout${EVENT_KEY}`, - MOUSEENTER: `mouseenter${EVENT_KEY}`, - MOUSELEAVE: `mouseleave${EVENT_KEY}` -} - /** * Class definition */ @@ -146,10 +135,6 @@ class Tooltip extends BaseComponent { return NAME } - static get Event() { - return Event - } - // Public enable() { this._isEnabled = true @@ -212,7 +197,7 @@ class Tooltip extends BaseComponent { return } - const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW) + const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW)) const shadowRoot = findShadowRoot(this._element) const isInTheDom = shadowRoot === null ? this._element.ownerDocument.documentElement.contains(this._element) : @@ -230,7 +215,7 @@ class Tooltip extends BaseComponent { if (!this._element.ownerDocument.documentElement.contains(this.tip)) { container.append(tip) - EventHandler.trigger(this._element, this.constructor.Event.INSERTED) + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)) } if (this._popper) { @@ -255,7 +240,7 @@ class Tooltip extends BaseComponent { const previousHoverState = this._isHovered this._isHovered = false - EventHandler.trigger(this._element, this.constructor.Event.SHOWN) + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN)) if (previousHoverState) { this._leave() @@ -270,7 +255,7 @@ class Tooltip extends BaseComponent { return } - const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE) + const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE)) if (hideEvent.defaultPrevented) { return } @@ -301,7 +286,7 @@ class Tooltip extends BaseComponent { } this._element.removeAttribute('aria-describedby') - EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN)) this._disposePopper() } @@ -484,14 +469,14 @@ class Tooltip extends BaseComponent { for (const trigger of triggers) { if (trigger === 'click') { - EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event)) + EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => this.toggle(event)) } else if (trigger !== TRIGGER_MANUAL) { const eventIn = trigger === TRIGGER_HOVER ? - this.constructor.Event.MOUSEENTER : - this.constructor.Event.FOCUSIN + this.constructor.eventName(EVENT_MOUSEENTER) : + this.constructor.eventName(EVENT_FOCUSIN) const eventOut = trigger === TRIGGER_HOVER ? - this.constructor.Event.MOUSELEAVE : - this.constructor.Event.FOCUSOUT + this.constructor.eventName(EVENT_MOUSELEAVE) : + this.constructor.eventName(EVENT_FOCUSOUT) EventHandler.on(this._element, eventIn, this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event) -- cgit v1.2.3 From 353ad45b4b8dd3235a9e26dcc614baaa7fa1a840 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 19 Feb 2022 16:16:51 +0200 Subject: Dropdown: use a combined selector to filter foreign not shown instances iteration (#35766) --- js/src/dropdown.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 5635ec96e..4bb1379f5 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -50,6 +50,7 @@ const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' +const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` const SELECTOR_MENU = '.dropdown-menu' const SELECTOR_NAVBAR = '.navbar' const SELECTOR_NAVBAR_NAV = '.navbar-nav' @@ -341,18 +342,14 @@ class Dropdown extends BaseComponent { return } - const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE) + const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN) - for (const toggle of toggles) { + for (const toggle of openToggles) { const context = Dropdown.getInstance(toggle) if (!context || context._config.autoClose === false) { continue } - if (!context._isShown()) { - continue - } - const composedPath = event.composedPath() const isMenuTarget = composedPath.includes(context._menu) if ( -- cgit v1.2.3 From cb8726d9e75b10c52c84753da171daacd78aee90 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 19 Feb 2022 16:22:32 +0200 Subject: Dropdown: use a better selector to avoid triggering click if button is disabled (#35866) --- js/src/dropdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 4bb1379f5..da56f4825 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -49,7 +49,7 @@ const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' -const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]' +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)' const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` const SELECTOR_MENU = '.dropdown-menu' const SELECTOR_NAVBAR = '.navbar' @@ -407,10 +407,6 @@ class Dropdown extends BaseComponent { event.preventDefault() event.stopPropagation() - if (isDisabled(this)) { - return - } - const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, delegateTarget.parentNode) const instance = Dropdown.getOrCreateInstance(getToggleButton) -- cgit v1.2.3 From e4b62a920a5ed9c48c46865f987702fa67369ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B8=D0=BB=D1=8F=D0=BD=20=D0=9F=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=D1=83=D0=B7=D0=BE=D0=B2?= Date: Thu, 27 Jan 2022 10:43:27 +0200 Subject: src/tooltip.js Optimization Util.findShadowRoot() returns either null or an object. It cannot return falsy, which allows this optimization. --- js/src/tooltip.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 5cf56ce6e..db626048f 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -199,9 +199,7 @@ class Tooltip extends BaseComponent { const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW)) const shadowRoot = findShadowRoot(this._element) - const isInTheDom = shadowRoot === null ? - this._element.ownerDocument.documentElement.contains(this._element) : - shadowRoot.contains(this._element) + const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element) if (showEvent.defaultPrevented || !isInTheDom) { return -- cgit v1.2.3 From c0f30366ace5f607e0b70a0e0034d2e8254bad9b Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Fri, 25 Feb 2022 08:53:17 -0800 Subject: Add centered dropdown and dropup options --- js/src/dropdown.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index da56f4825..2f7ca130f 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -48,6 +48,8 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' +const CLASS_NAME_DROPUP_CENTER = 'dropup-center' +const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)' const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` @@ -62,6 +64,8 @@ const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' +const PLACEMENT_TOPCENTER = 'top' +const PLACEMENT_BOTTOMCENTER = 'bottom' const Default = { offset: [0, 2], @@ -248,6 +252,14 @@ class Dropdown extends BaseComponent { return PLACEMENT_LEFT } + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { + return PLACEMENT_TOPCENTER + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { + return PLACEMENT_BOTTOMCENTER + } + // We need to trim the value because custom properties can also include spaces const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end' -- cgit v1.2.3 From bb7664db0aea2dddbc637992d2d0e78632dc2e68 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 1 Mar 2022 15:53:07 +0200 Subject: Dropdown: Simplify dataKeyApiHandler (#35870) * Dropdown.js: Remove duplicated check for `Not Shown` instance * Dropdown.js: Rearrange `dataApiKeydownHandler` checks * Dropdown: do some fixup inside `dataApiKeydownHandler` * Update dropdown.js Co-authored-by: XhmikosR --- js/src/dropdown.js | 53 ++++++++++++++--------------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 2f7ca130f..c93739b52 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -48,8 +48,6 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' -const CLASS_NAME_DROPUP_CENTER = 'dropup-center' -const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)' const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` @@ -64,8 +62,6 @@ const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' -const PLACEMENT_TOPCENTER = 'top' -const PLACEMENT_BOTTOMCENTER = 'bottom' const Default = { offset: [0, 2], @@ -252,14 +248,6 @@ class Dropdown extends BaseComponent { return PLACEMENT_LEFT } - if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { - return PLACEMENT_TOPCENTER - } - - if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { - return PLACEMENT_BOTTOMCENTER - } - // We need to trim the value because custom properties can also include spaces const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end' @@ -388,38 +376,27 @@ class Dropdown extends BaseComponent { } static dataApiKeydownHandler(event) { - // If not input/textarea: - // - And not a key in UP | DOWN | ESCAPE => not a dropdown command - // If input/textarea && If key is other than ESCAPE - // - If key is not UP or DOWN => not a dropdown command - // - If trigger inside the menu => not a dropdown command - - const { target, key, delegateTarget } = event - const isInput = /input|textarea/i.test(target.tagName) - const isEscapeEvent = key === ESCAPE_KEY - const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(key) - - if (!isInput && !(isUpOrDownEvent || isEscapeEvent)) { + // If not an UP | DOWN | ESCAPE key => not a dropdown command + // If input/textarea && if key is other than ESCAPE => not a dropdown command + + const isInput = /input|textarea/i.test(event.target.tagName) + const isEscapeEvent = event.key === ESCAPE_KEY + const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key) + + if (!isUpOrDownEvent && !isEscapeEvent) { return } if (isInput && !isEscapeEvent) { - // eslint-disable-next-line unicorn/no-lonely-if - if (!isUpOrDownEvent || target.closest(SELECTOR_MENU)) { - return - } - } - - const isActive = delegateTarget.classList.contains(CLASS_NAME_SHOW) - - if (!isActive && isEscapeEvent) { return } event.preventDefault() - event.stopPropagation() + if (!isEscapeEvent) { + event.stopPropagation() + } - const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, delegateTarget.parentNode) + const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode) const instance = Dropdown.getOrCreateInstance(getToggleButton) if (isEscapeEvent) { @@ -428,10 +405,8 @@ class Dropdown extends BaseComponent { return } - if (isUpOrDownEvent) { - instance.show() - instance._selectMenuItem(event) - } + instance.show() + instance._selectMenuItem(event) } } -- cgit v1.2.3 From 631cec4f70fa74b066c9d949aa5b8bf8cf06b46d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 03:33:14 +0300 Subject: Carousel: refactor dataApiKeyHandler to avoid use of `carouselInterface` --- js/src/carousel.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 7b22e9cbf..a5fe2597b 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -472,22 +472,22 @@ class Carousel extends BaseComponent { return } - const config = { - ...Manipulator.getDataAttributes(this) - } + event.preventDefault() + + const carousel = Carousel.getOrCreateInstance(target) const slideIndex = this.getAttribute('data-bs-slide-to') if (slideIndex) { - config.interval = false + carousel.to(slideIndex) + return } - Carousel.carouselInterface(target, config) - - if (slideIndex) { - Carousel.getInstance(target).to(slideIndex) + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { + carousel.next() + return } - event.preventDefault() + carousel.prev() } } -- cgit v1.2.3 From 13042d25cae2a8f5d43e24cd143cb1303aaed41e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 19 Feb 2022 19:02:51 +0200 Subject: Carousel: move logic of `dataApiClickHandler` --- js/src/carousel.js | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index a5fe2597b..12102f224 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -464,38 +464,36 @@ class Carousel extends BaseComponent { Carousel.carouselInterface(this, config) }) } +} - static dataApiClickHandler(event) { - const target = getElementFromSelector(this) - - if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { - return - } +/** + * Data API implementation + */ - event.preventDefault() +EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, function (event) { + const target = getElementFromSelector(this) - const carousel = Carousel.getOrCreateInstance(target) - const slideIndex = this.getAttribute('data-bs-slide-to') + if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { + return + } - if (slideIndex) { - carousel.to(slideIndex) - return - } + event.preventDefault() - if (Manipulator.getDataAttribute(this, 'slide') === 'next') { - carousel.next() - return - } + const carousel = Carousel.getOrCreateInstance(target) + const slideIndex = this.getAttribute('data-bs-slide-to') - carousel.prev() + if (slideIndex) { + carousel.to(slideIndex) + return } -} -/** - * Data API implementation - */ + if (Manipulator.getDataAttribute(this, 'slide') === 'next') { + carousel.next() + return + } -EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler) + carousel.prev() +}) EventHandler.on(window, EVENT_LOAD_DATA_API, () => { const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE) -- cgit v1.2.3 From eb8d5b43cedc76d88331f4d2f6ed3bca10e573ff Mon Sep 17 00:00:00 2001 From: GeoSot Date: Sat, 19 Feb 2022 19:04:50 +0200 Subject: Carousel: move `carouselInterface` inside `jqueryInterface` --- js/src/carousel.js | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 12102f224..4262d60df 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -432,36 +432,32 @@ class Carousel extends BaseComponent { } // Static - static carouselInterface(element, config) { - const data = Carousel.getOrCreateInstance(element, config) - - let { _config } = data - if (typeof config === 'object') { - _config = { - ..._config, - ...config + static jQueryInterface(config) { + return this.each(function () { + const data = Carousel.getOrCreateInstance(this, config) + + let { _config } = data + if (typeof config === 'object') { + _config = { + ..._config, + ...config + } } - } - const action = typeof config === 'string' ? config : _config.slide - - if (typeof config === 'number') { - data.to(config) - } else if (typeof action === 'string') { - if (typeof data[action] === 'undefined') { - throw new TypeError(`No method named "${action}"`) - } + const action = typeof config === 'string' ? config : _config.slide - data[action]() - } else if (_config.interval && _config.ride) { - data.pause() - data.cycle() - } - } + if (typeof config === 'number') { + data.to(config) + } else if (typeof action === 'string') { + if (typeof data[action] === 'undefined') { + throw new TypeError(`No method named "${action}"`) + } - static jQueryInterface(config) { - return this.each(function () { - Carousel.carouselInterface(this, config) + data[action]() + } else if (_config.interval && _config.ride) { + data.pause() + data.cycle() + } }) } } -- cgit v1.2.3 From c644f09d88a93445b415faa796f1d246ca34e2e7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Fri, 10 Sep 2021 11:57:58 +0300 Subject: Carousel: simplify carousel items selection We already know that carousel's parent is the carousel element, so we can use it explicitly --- js/src/carousel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 4262d60df..ea258527d 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -267,9 +267,7 @@ class Carousel extends BaseComponent { } _getItemIndex(element) { - this._items = element && element.parentNode ? - SelectorEngine.find(SELECTOR_ITEM, element.parentNode) : - [] + this._items = SelectorEngine.find(SELECTOR_ITEM, this._element) return this._items.indexOf(element) } -- cgit v1.2.3 From 63f30ac8ee28d12b5044c1f98e3b6ca7720a1f81 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Tue, 1 Mar 2022 17:08:12 +0200 Subject: Modal: refactor listeners to reduce some code noise (#35902) --- js/src/modal.js | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) (limited to 'js/src') diff --git a/js/src/modal.js b/js/src/modal.js index 054750c5f..ea8e0a046 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -69,6 +69,8 @@ class Modal extends BaseComponent { this._isShown = false this._isTransitioning = false this._scrollBar = new ScrollBarHelper() + + this._addEventListeners() } // Getters @@ -111,9 +113,6 @@ class Modal extends BaseComponent { this._adjustDialog() - this._toggleEscapeEventListener(true) - this._toggleResizeEventListener(true) - this._backdrop.show(() => this._showElement(relatedTarget)) } @@ -130,10 +129,6 @@ class Modal extends BaseComponent { this._isShown = false this._isTransitioning = true - - this._toggleEscapeEventListener(false) - this._toggleResizeEventListener(false) - this._focustrap.deactivate() this._element.classList.remove(CLASS_NAME_SHOW) @@ -217,12 +212,7 @@ class Modal extends BaseComponent { this._queueCallback(transitionComplete, this._dialog, this._isAnimated()) } - _toggleEscapeEventListener(enable) { - if (!enable) { - EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS) - return - } - + _addEventListeners() { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { if (event.key !== ESCAPE_KEY) { return @@ -236,15 +226,12 @@ class Modal extends BaseComponent { this._triggerBackdropTransition() }) - } - - _toggleResizeEventListener(enable) { - if (enable) { - EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog()) - return - } - EventHandler.off(window, EVENT_RESIZE) + EventHandler.on(window, EVENT_RESIZE, () => { + if (this._isShown && !this._isTransitioning) { + this._adjustDialog() + } + }) } _hideModal() { -- cgit v1.2.3 From 8d7358f23131a04762af95b88d045fde0627c79f Mon Sep 17 00:00:00 2001 From: Jann Westermann Date: Wed, 2 Mar 2022 01:20:37 +0100 Subject: Add static backdrop to offcanvas (#35832) * Add static backdrop option, to offcanvas * Trigger prevented event on esc with keyboard=false * Change offcanvas doc , moving backdrop examples to examples section --- js/src/offcanvas.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) (limited to 'js/src') diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index 2735a9c2a..b5afc0c87 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -39,6 +39,7 @@ const OPEN_SELECTOR = '.offcanvas.show' const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` +const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}` @@ -52,7 +53,7 @@ const Default = { } const DefaultType = { - backdrop: 'boolean', + backdrop: '(boolean|string)', keyboard: 'boolean', scroll: 'boolean' } @@ -164,12 +165,24 @@ class Offcanvas extends BaseComponent { // Private _initializeBackDrop() { + const clickCallback = () => { + if (this._config.backdrop === 'static') { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + return + } + + this.hide() + } + + // 'static' option will be translated to true, and booleans will keep their value + const isVisible = Boolean(this._config.backdrop) + return new Backdrop({ className: CLASS_NAME_BACKDROP, - isVisible: this._config.backdrop, + isVisible, isAnimated: true, rootElement: this._element.parentNode, - clickCallback: () => this.hide() + clickCallback: isVisible ? clickCallback : null }) } @@ -181,9 +194,16 @@ class Offcanvas extends BaseComponent { _addEventListeners() { EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { - if (this._config.keyboard && event.key === ESCAPE_KEY) { - this.hide() + if (event.key !== ESCAPE_KEY) { + return } + + if (!this._config.keyboard) { + EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED) + return + } + + this.hide() }) } -- cgit v1.2.3 From 6c40476af9b2d54fc8029294be1d9e4e8a246482 Mon Sep 17 00:00:00 2001 From: "louismaxime.piton" Date: Tue, 8 Mar 2022 12:34:21 +0100 Subject: Fix dropdowns --- js/src/dropdown.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index c93739b52..65b3aa372 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -48,6 +48,8 @@ const CLASS_NAME_SHOW = 'show' const CLASS_NAME_DROPUP = 'dropup' const CLASS_NAME_DROPEND = 'dropend' const CLASS_NAME_DROPSTART = 'dropstart' +const CLASS_NAME_DROPUP_CENTER = 'dropup-center' +const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center' const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)' const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` @@ -62,6 +64,8 @@ const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' +const PLACEMENT_TOPCENTER = 'top' +const PLACEMENT_BOTTOMCENTER = 'bottom' const Default = { offset: [0, 2], @@ -248,6 +252,14 @@ class Dropdown extends BaseComponent { return PLACEMENT_LEFT } + if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { + return PLACEMENT_TOPCENTER + } + + if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { + return PLACEMENT_BOTTOMCENTER + } + // We need to trim the value because custom properties can also include spaces const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end' @@ -400,8 +412,12 @@ class Dropdown extends BaseComponent { const instance = Dropdown.getOrCreateInstance(getToggleButton) if (isEscapeEvent) { - instance.hide() - getToggleButton.focus() + if (getToggleButton.classList.contains(CLASS_NAME_SHOW)) { + instance.hide() + getToggleButton.focus() + event.stopPropagation() + } + return } -- cgit v1.2.3 From e77ae50311366b07225d15b19e330a3871123437 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 00:57:28 +0200 Subject: Carousel: cleanup jQueryInterface Drop chained else ifs and unused variable. Since we were checking for `typeof config === 'string'` in both places, action was never `_config.slide`. --- js/src/carousel.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index ea258527d..afe02f5b7 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -442,17 +442,21 @@ class Carousel extends BaseComponent { } } - const action = typeof config === 'string' ? config : _config.slide - if (typeof config === 'number') { data.to(config) - } else if (typeof action === 'string') { - if (typeof data[action] === 'undefined') { - throw new TypeError(`No method named "${action}"`) + return + } + + if (typeof config === 'string') { + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) } - data[action]() - } else if (_config.interval && _config.ride) { + data[config]() + return + } + + if (_config.interval && _config.ride) { data.pause() data.cycle() } -- cgit v1.2.3 From a8142497c79f33bebd13a8b00d8104b324ce5cf7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 01:16:25 +0200 Subject: Carousel: reorder variables and refactor method to use it inline --- js/src/carousel.js | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index afe02f5b7..bc89a404f 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -277,17 +277,6 @@ class Carousel extends BaseComponent { return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) } - _triggerSlideEvent(relatedTarget, fromIndex, eventDirectionName) { - const targetIndex = this._getItemIndex(relatedTarget) - - return EventHandler.trigger(this._element, EVENT_SLIDE, { - relatedTarget, - direction: eventDirectionName, - from: fromIndex, - to: targetIndex - }) - } - _setActiveIndicatorElement(index) { if (!this._indicatorsElement) { return @@ -320,17 +309,12 @@ class Carousel extends BaseComponent { _slide(directionOrOrder, element) { const order = this._directionToOrder(directionOrOrder) + const activeElement = this._getActive() const activeElementIndex = this._getItemIndex(activeElement) - const nextElement = element || this._getItemByOrder(order, activeElement) + const nextElement = element || this._getItemByOrder(order, activeElement) const nextElementIndex = this._getItemIndex(nextElement) - const isCycling = Boolean(this._interval) - - const isNext = order === ORDER_NEXT - const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END - const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV - const eventDirectionName = this._orderToDirection(order) if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) { this._isSliding = false @@ -341,7 +325,17 @@ class Carousel extends BaseComponent { return } - const slideEvent = this._triggerSlideEvent(nextElement, activeElementIndex, eventDirectionName) + const triggerEvent = eventName => { + return EventHandler.trigger(this._element, eventName, { + relatedTarget: nextElement, + direction: this._orderToDirection(order), + from: activeElementIndex, + to: nextElementIndex + }) + } + + const slideEvent = triggerEvent(EVENT_SLIDE) + if (slideEvent.defaultPrevented) { return } @@ -353,6 +347,7 @@ class Carousel extends BaseComponent { this._isSliding = true + const isCycling = Boolean(this._interval) if (isCycling) { this.pause() } @@ -360,6 +355,10 @@ class Carousel extends BaseComponent { this._setActiveIndicatorElement(nextElementIndex) this._activeElement = nextElement + const isNext = order === ORDER_NEXT + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV + nextElement.classList.add(orderClassName) reflow(nextElement) @@ -375,12 +374,7 @@ class Carousel extends BaseComponent { this._isSliding = false - EventHandler.trigger(this._element, EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }) + triggerEvent(EVENT_SLID) } this._queueCallback(completeCallBack, activeElement, this._isAnimated()) -- cgit v1.2.3 From b7cce49dbc7d9dd6be1dd49e6764dd6f23f6d758 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 01:34:38 +0200 Subject: Carousel: use combined selector and drop variable used once --- js/src/carousel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index bc89a404f..f8ca1d638 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -56,8 +56,8 @@ const CLASS_NAME_NEXT = 'carousel-item-next' const CLASS_NAME_PREV = 'carousel-item-prev' const SELECTOR_ACTIVE = '.active' -const SELECTOR_ACTIVE_ITEM = '.active.carousel-item' const SELECTOR_ITEM = '.carousel-item' +const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM const SELECTOR_ITEM_IMG = '.carousel-item img' const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev' const SELECTOR_INDICATORS = '.carousel-indicators' -- cgit v1.2.3 From fcc2c809767525a32357fc4877a6dbbac6ba1370 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 02:04:03 +0200 Subject: Carousel: add a `getItems` helper --- js/src/carousel.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index f8ca1d638..fdc6736ad 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -95,7 +95,6 @@ class Carousel extends BaseComponent { constructor(element, config) { super(element, config) - this._items = null this._interval = null this._activeElement = null this._isPaused = false @@ -165,10 +164,8 @@ class Carousel extends BaseComponent { } to(index) { - this._activeElement = this._getActive() - const activeIndex = this._getItemIndex(this._activeElement) - - if (index > this._items.length - 1 || index < 0) { + const items = this._getItems() + if (index > items.length - 1 || index < 0) { return } @@ -177,17 +174,16 @@ class Carousel extends BaseComponent { return } + const activeIndex = this._getItemIndex(this._getActive()) if (activeIndex === index) { this.pause() this.cycle() return } - const order = index > activeIndex ? - ORDER_NEXT : - ORDER_PREV + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV - this._slide(order, this._items[index]) + this._slide(order, items[index]) } dispose() { @@ -267,14 +263,12 @@ class Carousel extends BaseComponent { } _getItemIndex(element) { - this._items = SelectorEngine.find(SELECTOR_ITEM, this._element) - - return this._items.indexOf(element) + return this._getItems().indexOf(element) } _getItemByOrder(order, activeElement) { const isNext = order === ORDER_NEXT - return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap) + return getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) } _setActiveIndicatorElement(index) { @@ -392,6 +386,10 @@ class Carousel extends BaseComponent { return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element) } + _getItems() { + return SelectorEngine.find(SELECTOR_ITEM, this._element) + } + _clearInterval() { if (this._interval) { clearInterval(this._interval) -- cgit v1.2.3 From 699402bee5d02659acaab69208549f26f9b3313d Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 02:07:36 +0200 Subject: Carousel: refactor `_slide` method te accept only order as first argument --- js/src/carousel.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index fdc6736ad..3a692657a 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -242,8 +242,8 @@ class Carousel extends BaseComponent { } const swipeConfig = { - leftCallback: () => this._slide(DIRECTION_LEFT), - rightCallback: () => this._slide(DIRECTION_RIGHT), + leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)), + rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)), endCallback: endCallBack } @@ -258,7 +258,7 @@ class Carousel extends BaseComponent { const direction = KEY_TO_DIRECTION[event.key] if (direction) { event.preventDefault() - this._slide(direction) + this._slide(this._directionToOrder(direction)) } } @@ -301,9 +301,7 @@ class Carousel extends BaseComponent { this._config.interval = elementInterval || this._config.defaultInterval } - _slide(directionOrOrder, element) { - const order = this._directionToOrder(directionOrOrder) - + _slide(order, element = null) { const activeElement = this._getActive() const activeElementIndex = this._getItemIndex(activeElement) -- cgit v1.2.3 From dd93551914424e577176a6377e1614742aa1018c Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 02:11:14 +0200 Subject: Carousel: refactor using inline function and move variables to the proper place --- js/src/carousel.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 3a692657a..086062251 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -266,11 +266,6 @@ class Carousel extends BaseComponent { return this._getItems().indexOf(element) } - _getItemByOrder(order, activeElement) { - const isNext = order === ORDER_NEXT - return getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) - } - _setActiveIndicatorElement(index) { if (!this._indicatorsElement) { return @@ -303,10 +298,8 @@ class Carousel extends BaseComponent { _slide(order, element = null) { const activeElement = this._getActive() - const activeElementIndex = this._getItemIndex(activeElement) - - const nextElement = element || this._getItemByOrder(order, activeElement) - const nextElementIndex = this._getItemIndex(nextElement) + const isNext = order === ORDER_NEXT + const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) { this._isSliding = false @@ -317,11 +310,13 @@ class Carousel extends BaseComponent { return } + const nextElementIndex = this._getItemIndex(nextElement) + const triggerEvent = eventName => { return EventHandler.trigger(this._element, eventName, { relatedTarget: nextElement, direction: this._orderToDirection(order), - from: activeElementIndex, + from: this._getItemIndex(activeElement), to: nextElementIndex }) } @@ -347,7 +342,6 @@ class Carousel extends BaseComponent { this._setActiveIndicatorElement(nextElementIndex) this._activeElement = nextElement - const isNext = order === ORDER_NEXT const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV -- cgit v1.2.3 From d4e87d28cdc1b5d053b4c08da737321ab4de2ff7 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 02:16:51 +0200 Subject: Carousel: small refactoring, remove unnecessary checks --- js/src/carousel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 086062251..f37ded7c2 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -301,8 +301,7 @@ class Carousel extends BaseComponent { const isNext = order === ORDER_NEXT const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) - if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE)) { - this._isSliding = false + if (nextElement === activeElement) { return } -- cgit v1.2.3 From 7e5a8016ba557641ee93b38aed4482a1360b64af Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 2 Mar 2022 02:18:55 +0200 Subject: Carousel: return early in `_slide` method --- js/src/carousel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index f37ded7c2..f5917eb7f 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -297,6 +297,10 @@ class Carousel extends BaseComponent { } _slide(order, element = null) { + if (this._isSliding) { + return + } + const activeElement = this._getActive() const isNext = order === ORDER_NEXT const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap) @@ -305,10 +309,6 @@ class Carousel extends BaseComponent { return } - if (this._isSliding) { - return - } - const nextElementIndex = this._getItemIndex(nextElement) const triggerEvent = eventName => { -- cgit v1.2.3 From 28f150d7204788114e2b36555f5e07eb8bdfdbab Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 00:01:55 +0200 Subject: Carousel: omit config merging in jQueryInterface after we create the instance This is already done inside `getOrCreateInstance` method --- js/src/carousel.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index f5917eb7f..37ebde3fc 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -417,14 +417,6 @@ class Carousel extends BaseComponent { return this.each(function () { const data = Carousel.getOrCreateInstance(this, config) - let { _config } = data - if (typeof config === 'object') { - _config = { - ..._config, - ...config - } - } - if (typeof config === 'number') { data.to(config) return @@ -439,7 +431,7 @@ class Carousel extends BaseComponent { return } - if (_config.interval && _config.ride) { + if (data._config.interval && data._config.ride) { data.pause() data.cycle() } -- cgit v1.2.3 From 88da704eedc5149b70dcec7845453456a6e26761 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 00:38:17 +0200 Subject: Carousel: omit redundant checks as we are always transforming the right values --- js/src/carousel.js | 8 -------- 1 file changed, 8 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 37ebde3fc..70c5fd286 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -389,10 +389,6 @@ class Carousel extends BaseComponent { } _directionToOrder(direction) { - if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { - return direction - } - if (isRTL()) { return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT } @@ -401,10 +397,6 @@ class Carousel extends BaseComponent { } _orderToDirection(order) { - if (![ORDER_NEXT, ORDER_PREV].includes(order)) { - return order - } - if (isRTL()) { return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT } -- cgit v1.2.3 From ec0e1c220e2f9b1f4591b4c7f12a622822f14014 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 01:00:33 +0200 Subject: Carousel: add comment for future fixes --- js/src/carousel.js | 1 + 1 file changed, 1 insertion(+) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 70c5fd286..edaec08f2 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -328,6 +328,7 @@ class Carousel extends BaseComponent { if (!activeElement || !nextElement) { // Some weirdness is happening, so we bail + // todo: change tests that use empty divs to avoid this check return } -- cgit v1.2.3 From 3673933fe74b4323267b0b0a7871a393ce2ff5cb Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 02:07:01 +0200 Subject: Carousel: rename private property --- js/src/carousel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index edaec08f2..5a9b2dc84 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -97,7 +97,7 @@ class Carousel extends BaseComponent { this._interval = null this._activeElement = null - this._isPaused = false + this._stayPaused = false this._isSliding = false this.touchTimeout = null this._swipeHelper = null @@ -139,7 +139,7 @@ class Carousel extends BaseComponent { pause(event) { if (!event) { - this._isPaused = true + this._stayPaused = true } if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) { @@ -152,11 +152,11 @@ class Carousel extends BaseComponent { cycle(event) { if (!event) { - this._isPaused = false + this._stayPaused = false } this._clearInterval() - if (this._config.interval && !this._isPaused) { + if (this._config.interval && !this._stayPaused) { this._updateInterval() this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval) -- cgit v1.2.3 From 6e904341c9a4a07ea0232850a2dcd4ddc7dfa00a Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 02:12:19 +0200 Subject: Carousel: change class check as it can only exist if carousel is sliding Also, fix the corresponding test --- js/src/carousel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'js/src') diff --git a/js/src/carousel.js b/js/src/carousel.js index 5a9b2dc84..7a30beb10 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -59,7 +59,6 @@ const SELECTOR_ACTIVE = '.active' const SELECTOR_ITEM = '.carousel-item' const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM const SELECTOR_ITEM_IMG = '.carousel-item img' -const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev' const SELECTOR_INDICATORS = '.carousel-indicators' const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]' const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]' @@ -142,7 +141,7 @@ class Carousel extends BaseComponent { this._stayPaused = true } - if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) { + if (this._isSliding) { triggerTransitionEnd(this._element) this.cycle(true) } -- cgit v1.2.3 From 135b9cdff2bb301e36faa9110043d5823f620b56 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 7 Apr 2022 02:36:08 +0300 Subject: Revamp tabs & follow ARIA 1.1 practices (#33079) * Tab: Revamp tab.js & add support Aria features * Tab: Add tab support, just to keep backwards compatibility. Better to remove it on v6 * Revert "Tab: Add tab support, just to keep backwards compatibility. Better to remove it on v6" * Support arrow down/up functionality * add prevent default to avoid scrolling the page during up/down keys handling * remove panel tabindex handling * Expand documentation text for JS plugin * Rearrange new docs to specifically call out a11y * properly place section Co-authored-by: XhmikosR Co-authored-by: Patrick H. Lauke Co-authored-by: Mark Otto --- js/src/tab.js | 281 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 197 insertions(+), 84 deletions(-) (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js index f9969fb7a..135e929dd 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -5,7 +5,7 @@ * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElementFromSelector, isDisabled, reflow } from './util/index' +import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index' import EventHandler from './dom/event-handler' import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' @@ -17,158 +17,264 @@ import BaseComponent from './base-component' const NAME = 'tab' const DATA_KEY = 'bs.tab' const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` -const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` +const EVENT_CLICK_DATA_API = `click${EVENT_KEY}` +const EVENT_KEYDOWN = `keydown${EVENT_KEY}` +const EVENT_LOAD_DATA_API = `load${EVENT_KEY}` + +const ARROW_LEFT_KEY = 'ArrowLeft' +const ARROW_RIGHT_KEY = 'ArrowRight' +const ARROW_UP_KEY = 'ArrowUp' +const ARROW_DOWN_KEY = 'ArrowDown' -const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu' const CLASS_NAME_ACTIVE = 'active' const CLASS_NAME_FADE = 'fade' const CLASS_NAME_SHOW = 'show' +const CLASS_DROPDOWN = 'dropdown' -const SELECTOR_DROPDOWN = '.dropdown' -const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' -const SELECTOR_ACTIVE = '.active' -const SELECTOR_ACTIVE_UL = ':scope > li > .active' -const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' -const SELECTOR_DROPDOWN_ACTIVE_CHILD = ':scope > .dropdown-menu .active' +const SELECTOR_DROPDOWN_MENU = '.dropdown-menu' +const SELECTOR_DROPDOWN_ITEM = '.dropdown-item' +const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)' + +const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]' +const SELECTOR_OUTER = '.nav-item, .list-group-item' +const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}` +const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]' // todo:v6: could be only `tab` +const SELECTOR_INNER_ELEM = `${SELECTOR_INNER}, ${SELECTOR_DATA_TOGGLE}` + +const SELECTOR_DATA_TOGGLE_ACTIVE = `.${CLASS_NAME_ACTIVE}[data-bs-toggle="tab"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="pill"], .${CLASS_NAME_ACTIVE}[data-bs-toggle="list"]` /** * Class definition */ class Tab extends BaseComponent { + constructor(element) { + super(element) + this._parent = this._element.closest(SELECTOR_TAB_PANEL) + + if (!this._parent) { + return + // todo: should Throw exception on v6 + // throw new TypeError(`${element.outerHTML} has not a valid parent ${SELECTOR_INNER_ELEM}`) + } + + // Set up initial aria attributes + this._setInitialAttributes(this._parent, this._getChildren()) + + EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)) + } + // Getters static get NAME() { return NAME } // Public - show() { - if (this._element.parentNode && - this._element.parentNode.nodeType === Node.ELEMENT_NODE && - this._element.classList.contains(CLASS_NAME_ACTIVE)) { + show() { // Shows this elem and deactivate the active sibling if exists + const innerElem = this._element + if (this._elemIsActive(innerElem)) { return } - const target = getElementFromSelector(this._element) - const listElement = this._element.closest(SELECTOR_NAV_LIST_GROUP) - let previous + // Search for active tab on same parent to deactivate it + const active = this._getActiveElem() - if (listElement) { - const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? SELECTOR_ACTIVE_UL : SELECTOR_ACTIVE - previous = SelectorEngine.find(itemSelector, listElement) - previous = previous[previous.length - 1] - } - - const hideEvent = previous ? - EventHandler.trigger(previous, EVENT_HIDE, { relatedTarget: this._element }) : + const hideEvent = active ? + EventHandler.trigger(active, EVENT_HIDE, { relatedTarget: innerElem }) : null - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, { relatedTarget: previous }) + const showEvent = EventHandler.trigger(innerElem, EVENT_SHOW, { relatedTarget: active }) - if (showEvent.defaultPrevented || (hideEvent !== null && hideEvent.defaultPrevented)) { + if (showEvent.defaultPrevented || (hideEvent && hideEvent.defaultPrevented)) { return } - this._activate(this._element, listElement) + this._deactivate(active, innerElem) + this._activate(innerElem, active) + } - const complete = () => { - EventHandler.trigger(previous, EVENT_HIDDEN, { relatedTarget: this._element }) - EventHandler.trigger(this._element, EVENT_SHOWN, { relatedTarget: previous }) + // Private + _activate(element, relatedElem) { + if (!element) { + return } - if (target) { - this._activate(target, target.parentNode, complete) - } else { - complete() - } - } + element.classList.add(CLASS_NAME_ACTIVE) - // Private - _activate(element, container, callback) { - const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? - SelectorEngine.find(SELECTOR_ACTIVE_UL, container) : - SelectorEngine.children(container, SELECTOR_ACTIVE) + this._activate(getElementFromSelector(element)) // Search and activate/show the proper section - const active = activeElements[0] - const isTransitioning = callback && (active && active.classList.contains(CLASS_NAME_FADE)) + const isAnimated = element.classList.contains(CLASS_NAME_FADE) + const complete = () => { + if (isAnimated) { // todo: maybe is redundant + element.classList.add(CLASS_NAME_SHOW) + } - const complete = () => this._transitionComplete(element, active, callback) + if (element.getAttribute('role') !== 'tab') { + return + } - if (active && isTransitioning) { - active.classList.remove(CLASS_NAME_SHOW) - this._queueCallback(complete, element, true) - } else { - complete() + element.focus() + element.removeAttribute('tabindex') + element.setAttribute('aria-selected', true) + this._toggleDropDown(element, true) + EventHandler.trigger(element, EVENT_SHOWN, { + relatedTarget: relatedElem + }) } + + this._queueCallback(complete, element, isAnimated) } - _transitionComplete(element, active, callback) { - if (active) { - active.classList.remove(CLASS_NAME_ACTIVE) + _deactivate(element, relatedElem) { + if (!element) { + return + } + + element.classList.remove(CLASS_NAME_ACTIVE) + element.blur() - const dropdownChild = SelectorEngine.findOne(SELECTOR_DROPDOWN_ACTIVE_CHILD, active.parentNode) + this._deactivate(getElementFromSelector(element)) // Search and deactivate the shown section too - if (dropdownChild) { - dropdownChild.classList.remove(CLASS_NAME_ACTIVE) + const isAnimated = element.classList.contains(CLASS_NAME_FADE) + const complete = () => { + if (isAnimated) { // todo maybe is redundant + element.classList.remove(CLASS_NAME_SHOW) } - if (active.getAttribute('role') === 'tab') { - active.setAttribute('aria-selected', false) + if (element.getAttribute('role') !== 'tab') { + return } + + element.setAttribute('aria-selected', false) + element.setAttribute('tabindex', '-1') + this._toggleDropDown(element, false) + EventHandler.trigger(element, EVENT_HIDDEN, { relatedTarget: relatedElem }) } - element.classList.add(CLASS_NAME_ACTIVE) - if (element.getAttribute('role') === 'tab') { - element.setAttribute('aria-selected', true) + this._queueCallback(complete, element, isAnimated) + } + + _keydown(event) { + if (!([ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key))) { + return + } + + event.stopPropagation()// stopPropagation/preventDefault both added to support up/down keys without scrolling the page + event.preventDefault() + const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key) + const nextActiveElement = getNextActiveElement(this._getChildren(), event.target, isNext, true) + Tab.getOrCreateInstance(nextActiveElement).show() + } + + _getChildren() { // collection of inner elements + return SelectorEngine.find(SELECTOR_INNER_ELEM, this._parent) + } + + _getActiveElem() { + return this._getChildren().find(child => this._elemIsActive(child)) || null + } + + _setInitialAttributes(parent, children) { + this._setAttributeIfNotExists(parent, 'role', 'tablist') + + for (const child of children) { + this._setInitialAttributesOnChild(child) } + } - reflow(element) + _setInitialAttributesOnChild(child) { + child = this._getInnerElement(child) + const isActive = this._elemIsActive(child) + const outerElem = this._getOuterElement(child) + child.setAttribute('aria-selected', isActive) - if (element.classList.contains(CLASS_NAME_FADE)) { - element.classList.add(CLASS_NAME_SHOW) + if (outerElem !== child) { + this._setAttributeIfNotExists(outerElem, 'role', 'presentation') } - let parent = element.parentNode - if (parent && parent.nodeName === 'LI') { - parent = parent.parentNode + if (!isActive) { + child.setAttribute('tabindex', '-1') } - if (parent && parent.classList.contains(CLASS_NAME_DROPDOWN_MENU)) { - const dropdownElement = element.closest(SELECTOR_DROPDOWN) + this._setAttributeIfNotExists(child, 'role', 'tab') - if (dropdownElement) { - for (const dropdown of SelectorEngine.find(SELECTOR_DROPDOWN_TOGGLE, dropdownElement)) { - dropdown.classList.add(CLASS_NAME_ACTIVE) - } - } + // set attributes to the related panel too + this._setInitialAttributesOnTargetPanel(child) + } + + _setInitialAttributesOnTargetPanel(child) { + const target = getElementFromSelector(child) - element.setAttribute('aria-expanded', true) + if (!target) { + return } - if (callback) { - callback() + this._setAttributeIfNotExists(target, 'role', 'tabpanel') + + if (child.id) { + this._setAttributeIfNotExists(target, 'aria-labelledby', `#${child.id}`) } } + _toggleDropDown(element, open) { + const outerElem = this._getOuterElement(element) + if (!outerElem.classList.contains(CLASS_DROPDOWN)) { + return + } + + const toggle = (selector, className) => { + const element = SelectorEngine.findOne(selector, outerElem) + if (element) { + element.classList.toggle(className, open) + } + } + + toggle(SELECTOR_DROPDOWN_TOGGLE, CLASS_NAME_ACTIVE) + toggle(SELECTOR_DROPDOWN_MENU, CLASS_NAME_SHOW) + toggle(SELECTOR_DROPDOWN_ITEM, CLASS_NAME_ACTIVE) + outerElem.setAttribute('aria-expanded', open) + } + + _setAttributeIfNotExists(element, attribute, value) { + if (!element.hasAttribute(attribute)) { + element.setAttribute(attribute, value) + } + } + + _elemIsActive(elem) { + return elem.classList.contains(CLASS_NAME_ACTIVE) + } + + // Try to get the inner element (usually the .nav-link) + _getInnerElement(elem) { + return elem.matches(SELECTOR_INNER_ELEM) ? elem : SelectorEngine.findOne(SELECTOR_INNER_ELEM, elem) + } + + // Try to get the outer element (usually the .nav-item) + _getOuterElement(elem) { + return elem.closest(SELECTOR_OUTER) || elem + } + // Static static jQueryInterface(config) { return this.each(function () { const data = Tab.getOrCreateInstance(this) - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } + if (typeof config !== 'string') { + return + } - data[config]() + if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { + throw new TypeError(`No method named "${config}"`) } + + data[config]() }) } } @@ -186,10 +292,17 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( return } - const data = Tab.getOrCreateInstance(this) - data.show() + Tab.getOrCreateInstance(this).show() }) +/** + * Initialize on focus + */ +EventHandler.on(window, EVENT_LOAD_DATA_API, () => { + for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE_ACTIVE)) { + Tab.getOrCreateInstance(element) + } +}) /** * jQuery */ -- cgit v1.2.3 From cfd2f3f7787ba22feb78d916956f6f73746f3ee3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 10 Mar 2022 13:24:47 +0200 Subject: Update dropdown.js minor refactoring --- js/src/dropdown.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'js/src') diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 65b3aa372..dfa9a63aa 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -404,25 +404,22 @@ class Dropdown extends BaseComponent { } event.preventDefault() - if (!isEscapeEvent) { - event.stopPropagation() - } const getToggleButton = SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode) const instance = Dropdown.getOrCreateInstance(getToggleButton) - if (isEscapeEvent) { - if (getToggleButton.classList.contains(CLASS_NAME_SHOW)) { - instance.hide() - getToggleButton.focus() - event.stopPropagation() - } - + if (isUpOrDownEvent) { + event.stopPropagation() + instance.show() + instance._selectMenuItem(event) return } - instance.show() - instance._selectMenuItem(event) + if (instance._isShown()) { // else is escape and we check if it is shown + event.stopPropagation() + instance.hide() + getToggleButton.focus() + } } } -- cgit v1.2.3 From ece16012270a9ef7781ce9269cb151c5e5961734 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 13 Apr 2022 20:29:13 +0300 Subject: Revamp Scrollspy using Intersection observer (#33421) * Revamp scrollspy to use IntersectionObserver * Add smooth scroll support * Update scrollspy.js/md * move functionality to method * Update scrollspy.js * Add SmoothScroll to docs example * Refactor Using `Maps` and smaller methods * Update scrollspy.md/js * Update scrollspy.spec.js * Support backwards compatibility * minor optimizations * Merge activation functionality * Update scrollspy.md * Update scrollspy.js * Rewording some of the documentation changes * Update scrollspy.js * Update scrollspy.md * tweaking calculation functionality & drop text that suggests, to deactivate target when wrapper is not visible * tweak calculation * Fix lint * Support scrollspy in body & tests * change doc example to a more valid solution Co-authored-by: XhmikosR Co-authored-by: Patrick H. Lauke --- js/src/dom/manipulator.js | 16 --- js/src/scrollspy.js | 264 +++++++++++++++++++++++++--------------------- 2 files changed, 144 insertions(+), 136 deletions(-) (limited to 'js/src') diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index e3ee293c7..5e6ad92ae 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -57,22 +57,6 @@ const Manipulator = { getDataAttribute(element, key) { return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)) - }, - - offset(element) { - const rect = element.getBoundingClientRect() - - return { - top: rect.top + window.pageYOffset, - left: rect.left + window.pageXOffset - } - }, - - position(element) { - return { - top: element.offsetTop, - left: element.offsetLeft - } } } diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 029970ed2..71c111a94 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -5,9 +5,8 @@ * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElement, getSelectorFromElement } from './util/index' +import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' @@ -21,34 +20,34 @@ const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const EVENT_ACTIVATE = `activate${EVENT_KEY}` -const EVENT_SCROLL = `scroll${EVENT_KEY}` +const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item' const CLASS_NAME_ACTIVE = 'active' const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]' +const SELECTOR_TARGET_LINKS = '[href]' const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' const SELECTOR_NAV_LINKS = '.nav-link' const SELECTOR_NAV_ITEMS = '.nav-item' const SELECTOR_LIST_ITEMS = '.list-group-item' -const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}` +const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}` const SELECTOR_DROPDOWN = '.dropdown' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' -const METHOD_OFFSET = 'offset' -const METHOD_POSITION = 'position' - const Default = { - offset: 10, - method: 'auto', - target: '' + offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: '0px 0px -25%', + smoothScroll: false, + target: null } const DefaultType = { - offset: 'number', - method: 'string', - target: '(string|element)' + offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons + rootMargin: 'string', + smoothScroll: 'boolean', + target: 'element' } /** @@ -58,16 +57,18 @@ const DefaultType = { class ScrollSpy extends BaseComponent { constructor(element, config) { super(element, config) - this._scrollElement = this._element.tagName === 'BODY' ? window : this._element - this._offsets = [] - this._targets = [] - this._activeTarget = null - this._scrollHeight = 0 - - EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process()) - this.refresh() - this._process() + // this._element is the observablesContainer and config.target the menu links wrapper + this._targetLinks = new Map() + this._observableSections = new Map() + this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element + this._activeTarget = null + this._observer = null + this._previousScrollData = { + visibleEntryTop: 0, + parentScrollTop: 0 + } + this.refresh() // initialize } // Getters @@ -85,145 +86,168 @@ class ScrollSpy extends BaseComponent { // Public refresh() { - this._offsets = [] - this._targets = [] - this._scrollHeight = this._getScrollHeight() - - const autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION - const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method - const offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0 - const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target) - .map(element => { - const targetSelector = getSelectorFromElement(element) - const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null - - if (!target) { - return null - } + this._initializeTargetsAndObservables() + this._maybeEnableSmoothScroll() - const targetBCR = target.getBoundingClientRect() - - return targetBCR.width || targetBCR.height ? - [Manipulator[offsetMethod](target).top + offsetBase, targetSelector] : - null - }) - .filter(Boolean) - .sort((a, b) => a[0] - b[0]) + if (this._observer) { + this._observer.disconnect() + } else { + this._observer = this._getNewObserver() + } - for (const target of targets) { - this._offsets.push(target[0]) - this._targets.push(target[1]) + for (const section of this._observableSections.values()) { + this._observer.observe(section) } } dispose() { - EventHandler.off(this._scrollElement, EVENT_KEY) + this._observer.disconnect() super.dispose() } // Private - _configAfterMerge(config) { - config.target = getElement(config.target) || document.documentElement + // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case + config.target = getElement(config.target) || document.body return config } - _getScrollTop() { - return this._scrollElement === window ? - this._scrollElement.pageYOffset : - this._scrollElement.scrollTop - } + _maybeEnableSmoothScroll() { + if (!this._config.smoothScroll) { + return + } - _getScrollHeight() { - return this._scrollElement.scrollHeight || Math.max( - document.body.scrollHeight, - document.documentElement.scrollHeight - ) - } + // unregister any previous listeners + EventHandler.off(this._config.target, EVENT_CLICK) + + EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { + const observableSection = this._observableSections.get(event.target.hash) + if (observableSection) { + event.preventDefault() + const root = this._rootElement || window + const height = observableSection.offsetTop - this._element.offsetTop + if (root.scrollTo) { + root.scrollTo({ top: height }) + return + } - _getOffsetHeight() { - return this._scrollElement === window ? - window.innerHeight : - this._scrollElement.getBoundingClientRect().height + // Chrome 60 doesn't support `scrollTo` + root.scrollTop = height + } + }) } - _process() { - const scrollTop = this._getScrollTop() + this._config.offset - const scrollHeight = this._getScrollHeight() - const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight() + _getNewObserver() { + const options = { + root: this._rootElement, + threshold: [0.1, 0.5, 1], + rootMargin: this._getRootMargin() + } + + return new IntersectionObserver(entries => this._observerCallback(entries), options) + } - if (this._scrollHeight !== scrollHeight) { - this.refresh() + // The logic of selection + _observerCallback(entries) { + const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`) + const activate = entry => { + this._previousScrollData.visibleEntryTop = entry.target.offsetTop + this._process(targetElement(entry)) } - if (scrollTop >= maxScroll) { - const target = this._targets[this._targets.length - 1] + const parentScrollTop = (this._rootElement || document.documentElement).scrollTop + const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop + this._previousScrollData.parentScrollTop = parentScrollTop + + for (const entry of entries) { + if (!entry.isIntersecting) { + this._activeTarget = null + this._clearActiveClass(targetElement(entry)) - if (this._activeTarget !== target) { - this._activate(target) + continue } - return - } + const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop + // if we are scrolling down, pick the bigger offsetTop + if (userScrollsDown && entryIsLowerThanPrevious) { + activate(entry) + // if parent isn't scrolled, let's keep the first visible item, breaking the iteration + if (!parentScrollTop) { + return + } - if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { - this._activeTarget = null - this._clear() - return + continue + } + + // if we are scrolling up, pick the smallest offsetTop + if (!userScrollsDown && !entryIsLowerThanPrevious) { + activate(entry) + } } + } + + // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only + _getRootMargin() { + return this._config.offset ? `${this._config.offset}px 0px -30%` : this._config.rootMargin + } + + _initializeTargetsAndObservables() { + this._targetLinks = new Map() + this._observableSections = new Map() + + const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target) - for (const i of this._offsets.keys()) { - const isActiveTarget = this._activeTarget !== this._targets[i] && - scrollTop >= this._offsets[i] && - (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]) + for (const anchor of targetLinks) { + // ensure that the anchor has an id and is not disabled + if (!anchor.hash || isDisabled(anchor)) { + continue + } + + const observableSection = SelectorEngine.findOne(anchor.hash, this._element) - if (isActiveTarget) { - this._activate(this._targets[i]) + // ensure that the observableSection exists & is visible + if (isVisible(observableSection)) { + this._targetLinks.set(anchor.hash, anchor) + this._observableSections.set(anchor.hash, observableSection) } } } - _activate(target) { - this._activeTarget = target - - this._clear() + _process(target) { + if (this._activeTarget === target) { + return + } - const queries = SELECTOR_LINK_ITEMS.split(',') - .map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`) + this._clearActiveClass(this._config.target) + this._activeTarget = target + target.classList.add(CLASS_NAME_ACTIVE) + this._activateParents(target) - const link = SelectorEngine.findOne(queries.join(','), this._config.target) + EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target }) + } - link.classList.add(CLASS_NAME_ACTIVE) - if (link.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { - SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, link.closest(SELECTOR_DROPDOWN)) + _activateParents(target) { + // Activate dropdown parents + if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)) .classList.add(CLASS_NAME_ACTIVE) - } else { - for (const listGroup of SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP)) { - // Set triggered links parents as active - // With both
    and