@natachah/vanilla-frontend 0.2.2 → 0.2.4

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.
@@ -102,13 +102,35 @@
102
102
  <p><code>false</code> by default, or a <code>string</code></p>
103
103
  </td>
104
104
  </tr>
105
+ <tr>
106
+ <td data-label="Name">
107
+ <p>trap.exclusions</p>
108
+ </td>
109
+ <td data-label="Description">
110
+ <p>The elements that should not be inert for Accessibility</p>
111
+ </td>
112
+ <td data-label="Value">
113
+ <p><code>['#backdrop']</code> by default, or an <code>Array</code></p>
114
+ </td>
115
+ </tr>
116
+ <tr>
117
+ <td data-label="Name">
118
+ <p>trap.inclusions</p>
119
+ </td>
120
+ <td data-label="Description">
121
+ <p>The elements to include into the <kbd>TAB</kbd> trap focus</p>
122
+ </td>
123
+ <td data-label="Value">
124
+ <p><code>null</code> by default, or an <code>Array</code></p>
125
+ </td>
126
+ </tr>
105
127
  </tbody>
106
128
  </table>
107
129
 
108
130
  <doc-code data-type="js">
109
131
  import Drawer from '@natachah/vanilla-frontend/js/utilities/_drawer.js'
110
132
  const drawer = document.getElementById('drawer')
111
- if (drawer) new Drawer(drawer, { breakpoint : 960, cookie: '_drawer-cookie' })
133
+ if (drawer) new Drawer(drawer, { breakpoint : 960, cookie: '_drawer-cookie': trap: {exclusions: ['#backdrop', '#toggleMenu'], inclusions: ['#toggleMenu']} })
112
134
  </doc-code>
113
135
 
114
136
  <blockquote>
@@ -15,11 +15,11 @@
15
15
 
16
16
  <h2>Javascript</h2>
17
17
  <p>This component is only in javascript, to use it you must import the javascript file and create a new Trap object.</p>
18
- <p>You can pass the element and an array of exclusions when initiate the component.</p>
18
+ <p>You can pass the element, an array of exclusions (= element that should not be inert) and an array of inclusions (= element that should be included into the trap) when initiate the component.</p>
19
19
 
20
20
  <doc-code data-type="js">
21
21
  import Trap from "@natachah/vanilla-frontend/js/utilities/_trap"
22
- new Trap(document.getElementById('drawer'), ['#backdrop'])
22
+ new Trap(document.getElementById('drawer'), ['#backdrop','#toggleMenu'], ['#toggleMenu'])
23
23
  </doc-code>
24
24
 
25
25
  <h3>Methods</h3>
@@ -18,7 +18,7 @@ class DocLayout extends HTMLElement {
18
18
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pin-angle" viewBox="0 0 16 16">
19
19
  <path d="M9.828.722a.5.5 0 0 1 .354.146l4.95 4.95a.5.5 0 0 1 0 .707c-.48.48-1.072.588-1.503.588-.177 0-.335-.018-.46-.039l-3.134 3.134a6 6 0 0 1 .16 1.013c.046.702-.032 1.687-.72 2.375a.5.5 0 0 1-.707 0l-2.829-2.828-3.182 3.182c-.195.195-1.219.902-1.414.707s.512-1.22.707-1.414l3.182-3.182-2.828-2.829a.5.5 0 0 1 0-.707c.688-.688 1.673-.767 2.375-.72a6 6 0 0 1 1.013.16l3.134-3.133a3 3 0 0 1-.04-.461c0-.43.108-1.022.589-1.503a.5.5 0 0 1 .353-.146m.122 2.112v-.002zm0-.002v.002a.5.5 0 0 1-.122.51L6.293 6.878a.5.5 0 0 1-.511.12H5.78l-.014-.004a5 5 0 0 0-.288-.076 5 5 0 0 0-.765-.116c-.422-.028-.836.008-1.175.15l5.51 5.509c.141-.34.177-.753.149-1.175a5 5 0 0 0-.192-1.054l-.004-.013v-.001a.5.5 0 0 1 .12-.512l3.536-3.535a.5.5 0 0 1 .532-.115l.096.022c.087.017.208.034.344.034q.172.002.343-.04L9.927 2.028q-.042.172-.04.343a1.8 1.8 0 0 0 .062.46z"/>
20
20
  </svg>
21
- 0.2.2
21
+ 0.2.4
22
22
  </span>
23
23
  </li>
24
24
  <li>
package/js/_drawer.js CHANGED
@@ -20,7 +20,10 @@ export default class Drawer extends BaseComponent {
20
20
  static OPTIONS = {
21
21
  breakpoint: 960,
22
22
  cookie: false,
23
- exclusions: ['#backdrop']
23
+ trap: {
24
+ exclusions: null,
25
+ inclusions: null
26
+ }
24
27
  }
25
28
 
26
29
  /**
@@ -34,6 +37,10 @@ export default class Drawer extends BaseComponent {
34
37
 
35
38
  if (options.breakpoint && typeof options.breakpoint !== 'number') throw new Error(ErrorMessage.typeOf('options.breakpoint', 'number'))
36
39
  if (options.cookie && typeof options.cookie !== 'string') throw new Error(ErrorMessage.typeOf('options.cookie', 'string'))
40
+ if (options.trap) {
41
+ if (options.trap.exclusions && typeof options.trap.exclusions !== 'object') throw new Error(ErrorMessage.typeOf('options.trap.exclusions', 'object'))
42
+ if (options.trap.inclusions && typeof options.trap.inclusions !== 'object') throw new Error(ErrorMessage.typeOf('options.trap.inclusions', 'object'))
43
+ }
37
44
 
38
45
  // Run the SUPER constructor from BaseComponent
39
46
  super(el, options, 'drawer')
@@ -49,9 +56,7 @@ export default class Drawer extends BaseComponent {
49
56
 
50
57
  this._cookie = this._options.cookie ? new Cookie(this._options.cookie) : null
51
58
 
52
- this._trap = new Trap(this._element, this._options.exclusions)
53
-
54
- console.log(this._buttons)
59
+ this._trap = new Trap(this._element, this._options.trap.exclusions ?? ['#backdrop'], this._options.trap.inclusions ?? [])
55
60
 
56
61
  // Init the event listener
57
62
  this.#init()
@@ -143,7 +148,7 @@ export default class Drawer extends BaseComponent {
143
148
  // * Need to wait the transition before make it focused
144
149
  this._element.addEventListener('transitionend', () => {
145
150
  if (this._element.getAttribute('role') === 'dialog') {
146
- this._isOpen ? this._trap.activate() : this._trap.desactivate()
151
+ this._isOpen ? this._trap.activate() : this._trap.deactivate()
147
152
  } else if (this._isOpen) {
148
153
  this._trap.focusOnFirst()
149
154
  }
@@ -38,6 +38,9 @@ describe('Structure of the class', () => {
38
38
 
39
39
  test('Constructor: Passing the correct parameters', () => {
40
40
  expect(() => new Drawer(document.getElementById('drawer'), { breakpoint: 'make-error' })).toThrowError(ErrorMessage.typeOf('options.breakpoint', 'number'))
41
+ expect(() => new Drawer(document.getElementById('drawer'), { cookie: [] })).toThrowError(ErrorMessage.typeOf('options.cookie', 'string'))
42
+ expect(() => new Drawer(document.getElementById('drawer'), { trap: { exclusions: 'error' } })).toThrowError(ErrorMessage.typeOf('options.trap.exclusions', 'object'))
43
+ expect(() => new Drawer(document.getElementById('drawer'), { trap: { inclusions: 'error' } })).toThrowError(ErrorMessage.typeOf('options.trap.inclusions', 'object'))
41
44
  })
42
45
 
43
46
  test('Constructor: Return the correct properties', () => {
@@ -7,9 +7,9 @@
7
7
  * - Constructor: Passing the correct parameters
8
8
  * - FocusOnFirst(): Passing the correct parameters
9
9
  * - Activate(): Save the new cookie value
10
- * - Desactivate(): Passing the correct parameters
10
+ * - Deactivate(): Passing the correct parameters
11
11
  * - HandleKeydown(): Verify the existance of a key in the cookie value
12
- * - GetFocusableElements(): Passing the correct parameters
12
+ * - GetFocusableElementss(): Passing the correct parameters
13
13
  */
14
14
 
15
15
  import { describe, test, expect, beforeAll, vi } from "vitest"
@@ -54,7 +54,7 @@ describe('Structure of the class', () => {
54
54
  test('Has all the public method', () => {
55
55
  expect(fakeTrap.focusOnFirst).toBeTypeOf('function')
56
56
  expect(fakeTrap.activate).toBeTypeOf('function')
57
- expect(fakeTrap.desactivate).toBeTypeOf('function')
57
+ expect(fakeTrap.deactivate).toBeTypeOf('function')
58
58
  expect(fakeTrap.handleKeydown).toBeTypeOf('function')
59
59
  expect(fakeTrap.getFocusableElements).toBeTypeOf('function')
60
60
  expect(fakeTrap.getSiblingsOutside).toBeTypeOf('function')
@@ -63,6 +63,7 @@ describe('Structure of the class', () => {
63
63
  test('Constructor: Passing the correct parameters', () => {
64
64
  expect(() => new Trap()).toThrowError(ErrorMessage.instanceOf('el', 'HTMLElement'))
65
65
  expect(() => new Trap(document.getElementById('trap'), 'error')).toThrowError(ErrorMessage.typeOf('exclusions', 'object'))
66
+ expect(() => new Trap(document.getElementById('trap'), null, 'error')).toThrowError(ErrorMessage.typeOf('inclusions', 'object'))
66
67
  })
67
68
 
68
69
  })
@@ -87,7 +88,7 @@ describe('FocusOnFirst()', () => {
87
88
  describe('Toggle the trap', () => {
88
89
 
89
90
  test('Activate the trap', () => {
90
- const eventSpy = vi.spyOn(fakeTrap._element, 'addEventListener')
91
+ const eventSpy = vi.spyOn(document, 'addEventListener')
91
92
 
92
93
  fakeTrap.activate()
93
94
 
@@ -95,8 +96,8 @@ describe('Toggle the trap', () => {
95
96
  expect(fakeTrap._items).not.toContain(fakeDisabledLink)
96
97
  expect(fakeTrap._first).toStrictEqual(document.getElementById('button'))
97
98
  expect(fakeTrap._last).toStrictEqual(document.getElementById('input'))
98
- expect(fakeTrap._inerts).toStrictEqual(fakeTrap.getSiblingsOutside())
99
- expect(fakeTrap._inerts).not.toContain(document.getElementById('backdrop'))
99
+ expect(fakeTrap._inertElements).toStrictEqual(fakeTrap.getSiblingsOutside())
100
+ expect(fakeTrap._inertElements).not.toContain(document.getElementById('backdrop'))
100
101
  expect(document.activeElement).toBe(fakeTrap._first)
101
102
  expect(eventSpy).toHaveBeenCalledWith('keydown', fakeTrap.handleKeydown)
102
103
  expect(document.getElementById('backdrop').hasAttribute('inert')).toBeFalsy()
@@ -106,9 +107,9 @@ describe('Toggle the trap', () => {
106
107
 
107
108
  })
108
109
 
109
- test('Desactivate the trap', () => {
110
- const eventSpy = vi.spyOn(fakeTrap._element, 'removeEventListener')
111
- fakeTrap.desactivate()
110
+ test('Deactivate the trap', () => {
111
+ const eventSpy = vi.spyOn(document, 'removeEventListener')
112
+ fakeTrap.deactivate()
112
113
  expect(eventSpy).toHaveBeenCalledWith('keydown', fakeTrap.handleKeydown)
113
114
  expect(document.getElementById('backdrop').hasAttribute('inert')).toBeFalsy()
114
115
  expect(document.getElementById('backdrop').hasAttribute('aria-hidden')).toBeFalsy()
@@ -17,21 +17,22 @@ export default class Trap {
17
17
  *
18
18
  * @constructor
19
19
  */
20
- constructor(el, exclusions = null) {
20
+ constructor(el, exclusions = null, inclusions = null) {
21
21
 
22
22
  // Check for errors
23
23
  if (!(el instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('el', 'HTMLElement'))
24
24
  if (exclusions && typeof exclusions !== 'object') throw new Error(ErrorMessage.typeOf('exclusions', 'object'))
25
+ if (inclusions && typeof inclusions !== 'object') throw new Error(ErrorMessage.typeOf('inclusions', 'object'))
25
26
 
26
27
  // Define elements
27
28
  this._element = el
28
29
  this._items = []
29
30
  this._first = null
30
31
  this._last = null
31
- this._inerts = []
32
- this._exclusions = exclusions ?? []
32
+ this._inertElements = []
33
+ this._exclusions = Array.isArray(exclusions) ? exclusions : Object.values(exclusions ?? {})
34
+ this._inclusions = Array.isArray(inclusions) ? inclusions : Object.values(inclusions ?? {})
33
35
  this.handleKeydown = this.handleKeydown.bind(this)
34
-
35
36
  }
36
37
 
37
38
  /**
@@ -47,7 +48,7 @@ export default class Trap {
47
48
  this._first = this._element.querySelector('[tabindex="0"]') ?? this._items[0]
48
49
 
49
50
  // Focus the first element by default
50
- this._first.focus()
51
+ if (this._first) this._first.focus()
51
52
  }
52
53
 
53
54
  /**
@@ -64,28 +65,27 @@ export default class Trap {
64
65
  this._last = this._items[this._items.length - 1]
65
66
 
66
67
  // Define event listener
67
- this._element.addEventListener('keydown', this.handleKeydown)
68
+ document.addEventListener('keydown', this.handleKeydown)
68
69
 
69
70
  // Focus the first element by default
70
71
  this._first.focus()
71
72
 
72
73
  // Make the rest of the content inert for screen reader
73
- this._inerts = this.getSiblingsOutside()
74
- this._inerts.forEach(el => {
74
+ this._inertElements = this.getSiblingsOutside()
75
+ this._inertElements.forEach(el => {
75
76
  el.setAttribute('aria-hidden', 'true')
76
77
  el.setAttribute('inert', true)
77
78
  })
78
-
79
79
  }
80
80
 
81
81
  /**
82
- * Desactivate the trap
82
+ * Deactivate the trap
83
83
  *
84
84
  */
85
- desactivate() {
86
- this._element.removeEventListener('keydown', this.handleKeydown)
85
+ deactivate() {
86
+ document.removeEventListener('keydown', this.handleKeydown)
87
87
 
88
- this._inerts.forEach(el => {
88
+ this._inertElements.forEach(el => {
89
89
  el.removeAttribute('aria-hidden')
90
90
  el.removeAttribute('inert')
91
91
  })
@@ -97,8 +97,6 @@ export default class Trap {
97
97
  * @param {event} e - Event
98
98
  */
99
99
  handleKeydown(e) {
100
-
101
- // Check for the Tab event only
102
100
  if (e.key !== 'Tab') return
103
101
 
104
102
  const isShift = e.shiftKey
@@ -110,17 +108,24 @@ export default class Trap {
110
108
  e.preventDefault()
111
109
  this._first.focus()
112
110
  }
113
-
114
111
  }
115
112
 
116
113
  /**
117
114
  * Get all focusable elements
118
115
  *
116
+ * @param {HTMLElement} element
117
+ * @returns {Array}
119
118
  */
120
119
  getFocusableElements() {
121
- return Array.from(
122
- this._element.querySelectorAll('a[href], area[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]),textarea:not([disabled]), button:not([disabled]), iframe, object, embed,[contenteditable], [tabindex]:not([tabindex="-1"])')
120
+ const focusables = Array.from(
121
+ this._element.querySelectorAll('a[href], area[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [contenteditable], [tabindex]:not([tabindex="-1"])')
123
122
  ).filter(el => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement)
123
+
124
+ const inclusions = Array.isArray(this._inclusions)
125
+ ? this._inclusions.flatMap(selector => Array.from(document.querySelectorAll(selector)))
126
+ : []
127
+
128
+ return [...focusables, ...inclusions]
124
129
  }
125
130
 
126
131
  /**
@@ -149,7 +154,5 @@ export default class Trap {
149
154
  }
150
155
 
151
156
  return Array.from(hiddenEls)
152
-
153
157
  }
154
-
155
- }
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@natachah/vanilla-frontend",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "A vanilla frontend framework",
5
5
  "keywords": [
6
6
  "html5",
Binary file