bootstrap-italia 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,11 +31,13 @@ const TAB_NAV_BACKWARD = 'backward';
31
31
  const Default = {
32
32
  autofocus: true,
33
33
  trapElement: null, // The element to trap focus inside of
34
+ initialFocus: null, // The inside element (optional) to set the focus after trapped
34
35
  };
35
36
 
36
37
  const DefaultType = {
37
38
  autofocus: 'boolean',
38
39
  trapElement: 'element',
40
+ initialFocus: '(null|element|string|function)',
39
41
  };
40
42
 
41
43
  /**
@@ -48,6 +50,7 @@ class FocusTrap extends Config {
48
50
  this._config = this._getConfig(config);
49
51
  this._isActive = false;
50
52
  this._lastTabNavDirection = null;
53
+ this._affectedElements = [];
51
54
  }
52
55
 
53
56
  // Getters
@@ -70,7 +73,7 @@ class FocusTrap extends Config {
70
73
  }
71
74
 
72
75
  if (this._config.autofocus) {
73
- this._config.trapElement.focus();
76
+ this._setInitialFocus();
74
77
  }
75
78
 
76
79
  EventHandler.off(document, EVENT_KEY); // guard against infinite focus loop
@@ -84,12 +87,30 @@ class FocusTrap extends Config {
84
87
  if (!this._isActive) {
85
88
  return
86
89
  }
87
-
88
90
  this._isActive = false;
91
+
89
92
  EventHandler.off(document, EVENT_KEY);
90
93
  }
91
94
 
92
95
  // Private
96
+ _setInitialFocus() {
97
+ if (this._config.initialFocus) {
98
+ let target;
99
+ if (typeof this._config.initialFocus === 'function') {
100
+ target = this._config.initialFocus();
101
+ } else {
102
+ target = this._config.initialFocus;
103
+ }
104
+ if (target && typeof target.focus === 'function') {
105
+ target.focus();
106
+ } else {
107
+ this._config.trapElement.focus();
108
+ }
109
+ } else {
110
+ this._config.trapElement.focus();
111
+ }
112
+ }
113
+
93
114
  _handleFocusin(event) {
94
115
  const { trapElement } = this._config;
95
116
 
@@ -98,7 +119,6 @@ class FocusTrap extends Config {
98
119
  }
99
120
 
100
121
  const elements = SelectorEngine.focusableChildren(trapElement);
101
-
102
122
  if (elements.length === 0) {
103
123
  trapElement.focus();
104
124
  } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {
@@ -1 +1 @@
1
- {"version":3,"file":"focustrap.js","sources":["../../../src/js/plugins/util/focustrap.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap Italia (https://italia.github.io/bootstrap-italia/)\n * Authors: https://github.com/italia/bootstrap-italia/blob/main/AUTHORS\n * Licensed under BSD-3-Clause license (https://github.com/italia/bootstrap-italia/blob/main/LICENSE)\n * This a fork of Bootstrap: Initial license and original file name below\n * Bootstrap (v5.2.3): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null, // The element to trap focus inside of\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element',\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._config.trapElement.focus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, (event) => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, (event) => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n\n this._isActive = false\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"focustrap.js","sources":["../../../src/js/plugins/util/focustrap.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap Italia (https://italia.github.io/bootstrap-italia/)\n * Authors: https://github.com/italia/bootstrap-italia/blob/main/AUTHORS\n * Licensed under BSD-3-Clause license (https://github.com/italia/bootstrap-italia/blob/main/LICENSE)\n * This a fork of Bootstrap: Initial license and original file name below\n * Bootstrap (v5.2.3): util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport EventHandler from '../dom/event-handler'\nimport SelectorEngine from '../dom/selector-engine'\nimport Config from './config'\n\n/**\n * Constants\n */\n\nconst NAME = 'focustrap'\nconst DATA_KEY = 'bs.focustrap'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY}`\n\nconst TAB_KEY = 'Tab'\nconst TAB_NAV_FORWARD = 'forward'\nconst TAB_NAV_BACKWARD = 'backward'\n\nconst Default = {\n autofocus: true,\n trapElement: null, // The element to trap focus inside of\n initialFocus: null, // The inside element (optional) to set the focus after trapped\n}\n\nconst DefaultType = {\n autofocus: 'boolean',\n trapElement: 'element',\n initialFocus: '(null|element|string|function)',\n}\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super()\n this._config = this._getConfig(config)\n this._isActive = false\n this._lastTabNavDirection = null\n this._affectedElements = []\n }\n\n // Getters\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n static get NAME() {\n return NAME\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return\n }\n\n if (this._config.autofocus) {\n this._setInitialFocus()\n }\n\n EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN, (event) => this._handleFocusin(event))\n EventHandler.on(document, EVENT_KEYDOWN_TAB, (event) => this._handleKeydown(event))\n\n this._isActive = true\n }\n\n deactivate() {\n if (!this._isActive) {\n return\n }\n this._isActive = false\n\n EventHandler.off(document, EVENT_KEY)\n }\n\n // Private\n _setInitialFocus() {\n if (this._config.initialFocus) {\n let target\n if (typeof this._config.initialFocus === 'function') {\n target = this._config.initialFocus()\n } else {\n target = this._config.initialFocus\n }\n if (target && typeof target.focus === 'function') {\n target.focus()\n } else {\n this._config.trapElement.focus()\n }\n } else {\n this._config.trapElement.focus()\n }\n }\n\n _handleFocusin(event) {\n const { trapElement } = this._config\n\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return\n }\n\n const elements = SelectorEngine.focusableChildren(trapElement)\n if (elements.length === 0) {\n trapElement.focus()\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus()\n } else {\n elements[0].focus()\n }\n }\n\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return\n }\n\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD\n }\n}\n\nexport default FocusTrap\n"],"names":[],"mappings":"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"}
package/dist/version.js CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  // NOTE:bootstrap italia version variable, useful to check for the current version
10
10
 
11
- const BOOTSTRAP_ITALIA_VERSION = '2.14.0';
11
+ const BOOTSTRAP_ITALIA_VERSION = '2.15.0';
12
12
 
13
13
  export { BOOTSTRAP_ITALIA_VERSION as default };
14
14
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../src/js/version.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap Italia (https://italia.github.io/bootstrap-italia/)\n * Authors: https://github.com/italia/bootstrap-italia/blob/main/AUTHORS\n * Licensed under BSD-3-Clause license (https://github.com/italia/bootstrap-italia/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// NOTE:bootstrap italia version variable, useful to check for the current version\n\nconst BOOTSTRAP_ITALIA_VERSION = '2.14.0'\nexport default BOOTSTRAP_ITALIA_VERSION\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEK,MAAC,wBAAwB,GAAG;;;;"}
1
+ {"version":3,"file":"version.js","sources":["../src/js/version.js"],"sourcesContent":["/**\n * --------------------------------------------------------------------------\n * Bootstrap Italia (https://italia.github.io/bootstrap-italia/)\n * Authors: https://github.com/italia/bootstrap-italia/blob/main/AUTHORS\n * Licensed under BSD-3-Clause license (https://github.com/italia/bootstrap-italia/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// NOTE:bootstrap italia version variable, useful to check for the current version\n\nconst BOOTSTRAP_ITALIA_VERSION = '2.15.0'\nexport default BOOTSTRAP_ITALIA_VERSION\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEK,MAAC,wBAAwB,GAAG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootstrap-italia",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "keywords": [
5
5
  "css",
6
6
  "sass",
@@ -7,14 +7,13 @@
7
7
  */
8
8
 
9
9
  import BaseComponent from './base-component.js'
10
-
11
10
  import { getElementFromSelector, isVisible, reflow } from './util/index'
12
11
  import EventHandler from './dom/event-handler'
13
12
  import SelectorEngine from './dom/selector-engine'
14
-
15
13
  import { isScreenMobile } from './util/device'
16
- import { getElementIndex } from './util/dom'
17
- import { disablePageScroll, enablePageScroll } from './util/pageScroll'
14
+ import ScrollBarHelper from './util/scrollbar'
15
+ import FocusTrap from './util/focustrap'
16
+ import Backdrop from './util/backdrop'
18
17
 
19
18
  const NAME = 'navbarcollapsible'
20
19
  const DATA_KEY = 'bs.navbarcollapsible'
@@ -23,7 +22,6 @@ const DATA_API_KEY = '.data-api'
23
22
 
24
23
  const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
25
24
  const EVENT_CLICK = `click${EVENT_KEY}`
26
- const EVENT_KEYUP = `keyup${EVENT_KEY}`
27
25
  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
28
26
  const EVENT_HIDE = `hide${EVENT_KEY}`
29
27
  const EVENT_HIDDEN = `hidden${EVENT_KEY}`
@@ -31,54 +29,88 @@ const EVENT_SHOW = `show${EVENT_KEY}`
31
29
  const EVENT_SHOWN = `shown${EVENT_KEY}`
32
30
  const EVENT_RESIZE = `resize${EVENT_KEY}`
33
31
 
34
- const CLASS_NAME_FADE = 'fade'
32
+ const CLASS_NAME_OPEN = 'navbar-open'
35
33
  const CLASS_NAME_SHOW = 'show'
36
34
  const CLASS_NAME_EXPANDED = 'expanded'
37
35
 
38
36
  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="navbarcollapsible"]'
39
-
40
- //const SELECTOR_NAVBAR = '.navbar-collapsable'
41
37
  const SELECTOR_BTN_CLOSE = '.close-div button'
42
38
  const SELECTOR_BTN_MENU_CLOSE = '.close-menu'
43
39
  const SELECTOR_BTN_BACK = '.it-back-button'
44
- const SELECTOR_OVERLAY = '.overlay'
45
40
  const SELECTOR_MENU_WRAPPER = '.menu-wrapper'
46
41
  const SELECTOR_NAVLINK = '.nav-link'
47
42
  const SELECTOR_MEGAMENUNAVLINK = '.nav-item .list-item'
48
43
  const SELECTOR_HEADINGLINK = '.it-heading-link'
49
44
  const SELECTOR_FOOTERLINK = '.it-footer-link'
50
45
 
46
+ const Default = {
47
+ backdrop: true,
48
+ focus: true,
49
+ }
50
+
51
+ const DefaultType = {
52
+ backdrop: '(boolean|string)',
53
+ focus: 'boolean',
54
+ }
55
+
51
56
  class NavBarCollapsible extends BaseComponent {
52
- constructor(element) {
53
- super(element)
57
+ constructor(element, config) {
58
+ super(element, config)
59
+
60
+ this._mainElement = SelectorEngine.findOne('main')
61
+ this._isNavbarOutsideMain = this._mainElement && !this._mainElement.contains(this._element)
62
+ this._parentElement = this._element.parentNode
54
63
 
55
64
  this._isShown = this._element.classList.contains(CLASS_NAME_EXPANDED)
56
- this._isTransitioning = false
57
65
 
58
- this._isMobile = isScreenMobile()
59
- this._isKeyShift = false
66
+ if (!this._element.getAttribute('tabindex')) {
67
+ this._element.setAttribute('tabindex', '-1')
68
+ }
60
69
 
61
- this._currItemIdx = 0
70
+ this._backdrop = this._initializeBackDrop()
71
+ this._focustrap = this._initializeFocusTrap()
72
+ this._scrollBar = new ScrollBarHelper()
73
+ this._isTransitioning = false
74
+ this._isMobile = isScreenMobile()
62
75
 
63
76
  this._btnClose = SelectorEngine.findOne(SELECTOR_BTN_CLOSE, this._element)
64
77
  this._btnBack = SelectorEngine.findOne(SELECTOR_BTN_BACK, this._element)
65
78
  this._menuWrapper = SelectorEngine.findOne(SELECTOR_MENU_WRAPPER, this._element)
66
- this._overlay = null
67
- this._setOverlay()
79
+
68
80
  this._menuItems = SelectorEngine.find(
69
81
  [SELECTOR_NAVLINK, SELECTOR_MEGAMENUNAVLINK, SELECTOR_HEADINGLINK, SELECTOR_FOOTERLINK, SELECTOR_BTN_MENU_CLOSE].join(','),
70
82
  this._element
71
83
  )
72
84
 
85
+ this._toggleButton =
86
+ SelectorEngine.findOne(`${SELECTOR_DATA_TOGGLE}[data-bs-target="#${this._element.id}"]`) ||
87
+ SelectorEngine.findOne(`${SELECTOR_DATA_TOGGLE}[href="#${this._element.id}"]`)
88
+
89
+ if (this._toggleButton) {
90
+ if (!this._toggleButton.getAttribute('aria-expanded')) {
91
+ this._toggleButton.setAttribute('aria-expanded', this._isShown ? 'true' : 'false')
92
+ }
93
+ }
73
94
  this._bindEvents()
74
95
  }
96
+
75
97
  // Getters
98
+ static get Default() {
99
+ return Default
100
+ }
101
+
102
+ static get DefaultType() {
103
+ return DefaultType
104
+ }
76
105
 
77
106
  static get NAME() {
78
107
  return NAME
79
108
  }
80
109
 
81
110
  // Public
111
+ toggle(relatedTarget) {
112
+ this._isShown ? this.hide() : this.show(relatedTarget)
113
+ }
82
114
 
83
115
  show(relatedTarget) {
84
116
  if (this._isShown || this._isTransitioning) {
@@ -93,14 +125,22 @@ class NavBarCollapsible extends BaseComponent {
93
125
  return
94
126
  }
95
127
 
128
+ this._isShown = true
129
+ this._isTransitioning = true
130
+ this._scrollBar.hide()
131
+
96
132
  if (this._btnBack) {
97
133
  this._btnBack.classList.add(CLASS_NAME_SHOW)
98
134
  }
99
135
 
100
- this._isShown = true
136
+ document.body.classList.add(CLASS_NAME_OPEN)
101
137
 
102
- disablePageScroll()
138
+ this._backdrop.show()
103
139
  this._showElement()
140
+
141
+ if (this._toggleButton) {
142
+ this._toggleButton.setAttribute('aria-expanded', 'true')
143
+ }
104
144
  }
105
145
 
106
146
  hide() {
@@ -116,45 +156,64 @@ class NavBarCollapsible extends BaseComponent {
116
156
 
117
157
  this._isShown = false
118
158
 
119
- const isAnimated = this._isAnimated()
120
-
121
- if (isAnimated) {
122
- this._isTransitioning = true
123
- }
159
+ this._isTransitioning = true
160
+ this._focustrap.deactivate()
124
161
 
125
162
  if (this._btnBack) {
126
163
  this._btnBack.classList.remove(CLASS_NAME_SHOW)
127
164
  }
128
- if (this._overlay) {
129
- this._overlay.classList.remove(CLASS_NAME_SHOW)
130
- }
131
165
 
132
166
  this._element.classList.remove(CLASS_NAME_EXPANDED)
133
167
 
134
- enablePageScroll()
135
- this._queueCallback(() => this._hideElement(), this._menuWrapper, isAnimated)
136
- }
168
+ this._backdrop.hide()
137
169
 
138
- toggle(relatedTarget) {
139
- this._isShown ? this.hide() : this.show(relatedTarget)
170
+ this._queueCallback(() => this._hideElement(), this._menuWrapper, this._isAnimated())
171
+
172
+ if (this._toggleButton) {
173
+ this._toggleButton.setAttribute('aria-expanded', 'false')
174
+ }
140
175
  }
141
176
 
142
177
  dispose() {
143
178
  if (typeof window !== 'undefined' && typeof document !== 'undefined') {
144
179
  EventHandler.off(window, EVENT_RESIZE)
145
- super.dispose()
180
+ EventHandler.off(document, EVENT_KEYDOWN)
146
181
  }
182
+ this._backdrop.dispose()
183
+
184
+ this._focustrap.deactivate()
185
+ super.dispose()
147
186
  }
148
187
 
149
- // Private
188
+ _initializeBackDrop() {
189
+ return new Backdrop({
190
+ isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value,
191
+ isAnimated: this._isAnimated(),
192
+ className: 'navbar-backdrop',
193
+ rootElement: this._parentElement,
194
+ clickCallback: () => {
195
+ this.hide()
196
+ },
197
+ })
198
+ }
199
+
200
+ _initializeFocusTrap() {
201
+ return new FocusTrap({
202
+ trapElement: this._element,
203
+ initialFocus: () => this._btnClose || this._element.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),
204
+ })
205
+ }
150
206
 
207
+ // Private
151
208
  _bindEvents() {
152
209
  if (typeof window !== 'undefined' && typeof document !== 'undefined') {
153
210
  EventHandler.on(window, EVENT_RESIZE, () => this._onResize())
211
+ EventHandler.on(document, EVENT_KEYDOWN, (evt) => {
212
+ if (this._isShown && evt.key === 'Escape') {
213
+ this.hide()
214
+ }
215
+ })
154
216
 
155
- if (this._overlay) {
156
- EventHandler.on(this._overlay, EVENT_CLICK, () => this.hide())
157
- }
158
217
  EventHandler.on(this._btnClose, EVENT_CLICK, (evt) => {
159
218
  evt.preventDefault()
160
219
  this.hide()
@@ -163,11 +222,8 @@ class NavBarCollapsible extends BaseComponent {
163
222
  evt.preventDefault()
164
223
  this.hide()
165
224
  })
166
-
167
225
  this._menuItems.forEach((item) => {
168
226
  EventHandler.on(item, EVENT_KEYDOWN, (evt) => this._isMobile && this._onMenuItemKeyDown(evt))
169
- EventHandler.on(item, EVENT_KEYUP, (evt) => this._isMobile && this._onMenuItemKeyUp(evt))
170
- EventHandler.on(item, EVENT_CLICK, (evt) => this._isMobile && this._onMenuItemClick(evt))
171
227
  })
172
228
  }
173
229
  }
@@ -176,30 +232,14 @@ class NavBarCollapsible extends BaseComponent {
176
232
  this._isMobile = isScreenMobile()
177
233
  }
178
234
 
179
- _onMenuItemKeyUp(evt) {
180
- if (evt.key === 'Shift') {
181
- this._isKeyShift = false
182
- }
183
- }
184
235
  _onMenuItemKeyDown(evt) {
185
- if (evt.key === 'Shift') {
186
- this._isKeyShift = true
187
- }
188
- if (evt.key === 'Tab') {
189
- evt.preventDefault()
190
- this._focusNext()
236
+ if (evt.key === 'Escape') {
237
+ this.hide()
191
238
  }
192
239
  }
193
- /**
194
- * Update the last focused element when an interactive element is clicked
195
- */
196
- _onMenuItemClick(evt) {
197
- this.currItemIdx = getElementIndex(evt.currentTarget, this._menuItems)
198
- }
199
240
 
200
241
  _isAnimated() {
201
- //there's no an animation css class you can toggle with a "show" css class, so it is supposed true
202
- return true //this._element.classList.contains(CLASS_NAME_EXPANDED)
242
+ return true
203
243
  }
204
244
 
205
245
  _isElementHidden(element) {
@@ -207,108 +247,47 @@ class NavBarCollapsible extends BaseComponent {
207
247
  }
208
248
 
209
249
  _showElement() {
210
- const isAnimated = this._isAnimated()
211
-
212
250
  this._element.style.display = 'block'
213
- this._element.removeAttribute('aria-hidden')
214
- this._element.setAttribute('aria-expanded', true)
215
- //this._element.setAttribute('role', 'dialog')
216
- if (this._overlay) {
217
- this._overlay.style.display = 'block'
251
+ if (!this._element.getAttribute('aria-label') && !this._element.getAttribute('aria-labelledby')) {
252
+ this._element.setAttribute('aria-label', 'Menu')
218
253
  }
254
+ this._element.setAttribute('aria-modal', true)
255
+ this._element.setAttribute('role', 'dialog')
219
256
 
220
- if (isAnimated) {
221
- reflow(this._element)
257
+ if (this._mainElement && this._isNavbarOutsideMain) {
258
+ this._mainElement.setAttribute('inert', '')
222
259
  }
223
260
 
261
+ reflow(this._element)
262
+
224
263
  this._element.classList.add(CLASS_NAME_EXPANDED)
225
- if (this._overlay) {
226
- this._overlay.classList.add(CLASS_NAME_SHOW)
227
- }
228
264
 
229
265
  const transitionComplete = () => {
230
- this._isTransitioning = false
231
- const firstItem = this._getNextVisibleItem(0) //at pos 0 there's the close button
232
- if (firstItem.item) {
233
- firstItem.item.focus()
234
- this._currItemIdx = firstItem.index
266
+ if (this._config.focus) {
267
+ this._focustrap.activate()
235
268
  }
269
+ this._isTransitioning = false
236
270
  EventHandler.trigger(this._element, EVENT_SHOWN)
237
271
  }
238
272
 
239
- this._queueCallback(transitionComplete, this._menuWrapper, isAnimated)
273
+ this._queueCallback(transitionComplete, this._menuWrapper, this._isAnimated())
240
274
  }
241
275
 
242
276
  _hideElement() {
243
- if (this._overlay) {
244
- this._overlay.style.display = 'none'
245
- }
246
-
247
277
  this._element.style.display = 'none'
248
- this._element.setAttribute('aria-hidden', true)
249
- this._element.removeAttribute('aria-expanded')
250
- //this._element.removeAttribute('aria-modal')
251
- //this._element.removeAttribute('role')
252
- this._isTransitioning = false
253
- EventHandler.trigger(this._element, EVENT_HIDDEN)
254
- }
278
+ this._element.removeAttribute('aria-modal')
279
+ this._element.removeAttribute('role')
255
280
 
256
- _setOverlay() {
257
- this._overlay = SelectorEngine.findOne(SELECTOR_OVERLAY, this._element)
258
- if (this._isAnimated) {
259
- this._overlay.classList.add(CLASS_NAME_FADE)
260
- }
261
- }
281
+ document.body.classList.remove(CLASS_NAME_OPEN)
262
282
 
263
- /**
264
- * Moves focus to the next focusable element based on the DOM exploration direction
265
- */
266
- _focusNext() {
267
- let nextIdx = this._currItemIdx + (this._isKeyShift ? -1 : 1)
268
- if (nextIdx < 0) {
269
- nextIdx = this._menuItems.length - 1
270
- } else if (nextIdx >= this._menuItems.length) {
271
- nextIdx = 0
272
- }
273
- const target = this._getNextVisibleItem(nextIdx, this._isKeyShift)
274
- if (target.item) {
275
- target.item.focus()
276
- this._currItemIdx = target.index
277
- }
278
- }
279
- /**
280
- * Get the next focusable element from a starting point
281
- * @param {int} start - the index of the array of the elements as starting point (included)
282
- * @param {boolean} wayTop - the array search direction (true: bottom to top, false: top to bottom)
283
- * @returns {Object} the item found and its index in the array
284
- */
285
- _getNextVisibleItem(start, wayTop) {
286
- let found = null
287
- let foundIdx = null
288
-
289
- let i = start
290
- let incr = wayTop ? -1 : 1
291
- let firstCheck = false
292
- while (!found && (i != start || !firstCheck)) {
293
- if (i == start) {
294
- firstCheck = true
295
- }
296
- if (!this._isElementHidden(this._menuItems[i])) {
297
- found = this._menuItems[i]
298
- foundIdx = i
299
- }
300
- i = i + incr
301
- if (i < 0) {
302
- i = this._menuItems.length - 1
303
- } else if (i >= this._menuItems.length) {
304
- i = 0
305
- }
283
+ if (this._mainElement && this._isNavbarOutsideMain) {
284
+ this._mainElement.removeAttribute('inert')
306
285
  }
307
286
 
308
- return {
309
- item: found,
310
- index: foundIdx,
311
- }
287
+ this._scrollBar.reset()
288
+ this._isTransitioning = false
289
+
290
+ EventHandler.trigger(this._element, EVENT_HIDDEN)
312
291
  }
313
292
  }
314
293
 
@@ -25,6 +25,7 @@ const SELECTOR_TOGGLER = '.custom-navbar-toggler'
25
25
  const SELECTOR_TOGGLER_ICON = '.it-list'
26
26
  const SELECTOR_COLLAPSIBLE = '.navbar-collapsable'
27
27
  const SELECTOR_PROGRESS_BAR = '.it-navscroll-progressbar'
28
+ const SELECTOR_MENU_WRAPPER = '.menu-wrapper'
28
29
 
29
30
  const Default = {
30
31
  scrollPadding: 10,
@@ -44,6 +45,11 @@ class NavScroll extends BaseComponent {
44
45
  this._callbackQueue = []
45
46
  this._scrollCb = null
46
47
 
48
+ const menuWrapper = SelectorEngine.findOne(SELECTOR_MENU_WRAPPER, this._element)
49
+ if (menuWrapper && !menuWrapper.hasAttribute('tabindex')) {
50
+ menuWrapper.setAttribute('tabindex', '-1')
51
+ }
52
+
47
53
  this._bindEvents()
48
54
  }
49
55
  // Getters
@@ -147,13 +153,31 @@ class NavScroll extends BaseComponent {
147
153
  }
148
154
 
149
155
  _scrollToHash(hash) {
150
- const target = SelectorEngine.findOne(hash, this._sectionContainer)
156
+ if (!hash || hash === '#') {
157
+ // Validate hash to prevent errors
158
+ return
159
+ }
160
+ const target = this._sectionContainer // Fallback: when container is null, omit the second parameter entirely
161
+ ? SelectorEngine.findOne(hash, this._sectionContainer)
162
+ : SelectorEngine.findOne(hash)
151
163
  if (target) {
152
164
  documentScrollTo(target.offsetTop - this._getScrollPadding(), {
153
165
  duration: this._config.duration,
154
166
  easing: this._config.easing,
155
- /*complete: () => {
156
- },*/
167
+ complete: () => {
168
+ const isHeading = target.matches('h1, h2, h3, h4, h5, h6')
169
+ const needsTabIndex = !target.hasAttribute('tabindex')
170
+ if (needsTabIndex) {
171
+ target.setAttribute('tabindex', '-1')
172
+ }
173
+ target.focus({ preventScroll: true }) // preventScroll to avoid double scrolling
174
+ if (needsTabIndex && isHeading) {
175
+ // remove tabIndex for headings after 500ms
176
+ setTimeout(() => {
177
+ target.removeAttribute('tabindex')
178
+ }, 500)
179
+ }
180
+ },
157
181
  })
158
182
 
159
183
  if (history.pushState) {
@@ -30,11 +30,13 @@ const TAB_NAV_BACKWARD = 'backward'
30
30
  const Default = {
31
31
  autofocus: true,
32
32
  trapElement: null, // The element to trap focus inside of
33
+ initialFocus: null, // The inside element (optional) to set the focus after trapped
33
34
  }
34
35
 
35
36
  const DefaultType = {
36
37
  autofocus: 'boolean',
37
38
  trapElement: 'element',
39
+ initialFocus: '(null|element|string|function)',
38
40
  }
39
41
 
40
42
  /**
@@ -47,6 +49,7 @@ class FocusTrap extends Config {
47
49
  this._config = this._getConfig(config)
48
50
  this._isActive = false
49
51
  this._lastTabNavDirection = null
52
+ this._affectedElements = []
50
53
  }
51
54
 
52
55
  // Getters
@@ -69,7 +72,7 @@ class FocusTrap extends Config {
69
72
  }
70
73
 
71
74
  if (this._config.autofocus) {
72
- this._config.trapElement.focus()
75
+ this._setInitialFocus()
73
76
  }
74
77
 
75
78
  EventHandler.off(document, EVENT_KEY) // guard against infinite focus loop
@@ -83,12 +86,30 @@ class FocusTrap extends Config {
83
86
  if (!this._isActive) {
84
87
  return
85
88
  }
86
-
87
89
  this._isActive = false
90
+
88
91
  EventHandler.off(document, EVENT_KEY)
89
92
  }
90
93
 
91
94
  // Private
95
+ _setInitialFocus() {
96
+ if (this._config.initialFocus) {
97
+ let target
98
+ if (typeof this._config.initialFocus === 'function') {
99
+ target = this._config.initialFocus()
100
+ } else {
101
+ target = this._config.initialFocus
102
+ }
103
+ if (target && typeof target.focus === 'function') {
104
+ target.focus()
105
+ } else {
106
+ this._config.trapElement.focus()
107
+ }
108
+ } else {
109
+ this._config.trapElement.focus()
110
+ }
111
+ }
112
+
92
113
  _handleFocusin(event) {
93
114
  const { trapElement } = this._config
94
115
 
@@ -97,7 +118,6 @@ class FocusTrap extends Config {
97
118
  }
98
119
 
99
120
  const elements = SelectorEngine.focusableChildren(trapElement)
100
-
101
121
  if (elements.length === 0) {
102
122
  trapElement.focus()
103
123
  } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {
package/src/js/version.js CHANGED
@@ -8,5 +8,5 @@
8
8
 
9
9
  // NOTE:bootstrap italia version variable, useful to check for the current version
10
10
 
11
- const BOOTSTRAP_ITALIA_VERSION = '2.14.0'
11
+ const BOOTSTRAP_ITALIA_VERSION = '2.15.0'
12
12
  export default BOOTSTRAP_ITALIA_VERSION
@@ -1,3 +1,3 @@
1
1
  :root {
2
- --bootstrap-italia-version: '2.14.0';
2
+ --bootstrap-italia-version: '2.15.0';
3
3
  }