nsw-design-system 3.2.5 → 3.3.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.
@@ -1,38 +1,107 @@
1
+ import { uniqueId } from '../../global/scripts/helpers/utilities'
2
+
1
3
  class Filters {
2
4
  constructor(element) {
3
5
  this.filters = element
6
+ this.filtersWrapper = element.querySelector('.nsw-filters__wrapper')
4
7
  this.openButton = element.querySelector('.nsw-filters__controls button')
5
- this.closeButtons = element.querySelectorAll('.nsw-filters__back button')
6
- this.all = element.querySelectorAll('.nsw-filters__all')
7
- this.allBlocks = Array.prototype.slice.call(this.all)
8
+ this.openButtonIcons = this.openButton ? this.openButton.querySelectorAll('span') : null
9
+ this.selectedCount = element.querySelector('.js-filters--count')
10
+ this.openButtonText = this.selectedCount ? this.selectedCount.querySelector('span:not(.nsw-material-icons)') : null
11
+ this.buttonLabel = this.openButtonText ? this.openButtonText.innerText : null
12
+ this.closeButton = element.querySelector('.nsw-filters__back button')
13
+ this.acceptButton = element.querySelector('.nsw-filters__accept button')
14
+ this.clearButton = element.querySelector('.nsw-filters__cancel button')
8
15
  this.showMoreButtons = Array.prototype.slice.call(element.querySelectorAll('.nsw-filters__more'))
16
+ this.accordionButtons = element.querySelectorAll('.nsw-filters__item-button')
17
+ this.showAll = element.querySelectorAll('.nsw-filters__all')
18
+ this.showAllBlocks = Array.prototype.slice.call(this.showAll)
19
+ this.filtersItems = element.querySelectorAll('.nsw-filters__item')
20
+ /* eslint-disable max-len */
21
+ this.focusableEls = this.filtersWrapper.querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])')
22
+ this.checkIcon = '<span class="material-icons nsw-material-icons" focusable="false" aria-hidden="true">check_circle</span>'
23
+ this.arrowIcon = '<span class="material-icons nsw-material-icons" focusable="false" aria-hidden="true">keyboard_arrow_right</span>'
24
+ /* eslint-ensable max-len */
9
25
  this.showEvent = (e) => this.showFilters(e)
10
26
  this.hideEvent = (e) => this.hideFilters(e)
11
27
  this.showMoreEvent = (e) => this.showMore(e)
28
+ this.toggleEvent = (e) => this.toggleAccordion(e)
29
+ this.resetEvent = (e) => this.clearAllFilters(e)
12
30
  this.body = document.body
31
+ this.buttons = []
32
+ this.content = []
33
+ this.selected = []
13
34
  }
14
35
 
15
36
  init() {
37
+ this.setUpDom()
16
38
  this.controls()
39
+ this.selectedItems()
17
40
  }
18
41
 
19
- controls() {
20
- this.openButton.addEventListener('click', this.showEvent, false)
42
+ setUpDom() {
43
+ this.filters.classList.add('ready')
44
+
45
+ if (this.openButton) {
46
+ if (this.openButtonIcons.length < 3) {
47
+ this.openButton.insertAdjacentHTML('beforeend', this.arrowIcon)
48
+ }
49
+ }
50
+
51
+ this.accordionButtons.forEach((button) => {
52
+ const buttonElem = button
53
+ const uID = uniqueId('collapsed')
54
+ buttonElem.setAttribute('type', 'button')
55
+ buttonElem.setAttribute('aria-expanded', 'false')
56
+ buttonElem.setAttribute('aria-controls', uID)
21
57
 
22
- this.closeButtons.forEach((element) => {
23
- element.addEventListener('click', this.hideEvent, false)
58
+ const contentElem = buttonElem.nextElementSibling
59
+ contentElem.id = buttonElem.getAttribute('aria-controls')
60
+ contentElem.hidden = true
61
+
62
+ this.content.push(contentElem)
63
+ this.buttons.push(buttonElem)
24
64
  })
65
+ }
66
+
67
+ controls() {
68
+ if (this.openButton) {
69
+ this.openButton.addEventListener('click', this.showEvent, false)
70
+ }
25
71
 
26
- this.all.forEach((element) => {
72
+ if (this.acceptButton) {
73
+ this.acceptButton.disabled = true
74
+ }
75
+
76
+ if (this.closeButton) {
77
+ this.closeButton.addEventListener('click', this.hideEvent, false)
78
+ }
79
+
80
+ this.showAll.forEach((element) => {
27
81
  const showMoreButton = element.nextElementSibling
28
82
  showMoreButton.addEventListener('click', this.showMoreEvent, false)
29
83
  })
84
+
85
+ if (this.buttons) {
86
+ this.buttons.forEach((element) => {
87
+ element.addEventListener('click', this.toggleEvent, false)
88
+ })
89
+ }
90
+
91
+ if (this.clearButton) {
92
+ this.clearButton.addEventListener('click', this.resetEvent, false)
93
+ }
30
94
  }
31
95
 
32
96
  showFilters(e) {
33
97
  e.preventDefault()
34
- this.filters.classList.add('active')
35
- this.body.classList.add('filters-open')
98
+ if (this.filters.classList.contains('nsw-filters--down')) {
99
+ this.filters.classList.toggle('active')
100
+ } else {
101
+ this.trapFocus(this.filtersWrapper)
102
+ this.filters.classList.add('active')
103
+ this.body.classList.add('filters-open')
104
+ }
36
105
  }
37
106
 
38
107
  hideFilters(e) {
@@ -45,10 +114,249 @@ class Filters {
45
114
  e.preventDefault()
46
115
  const currentShowMore = e.target
47
116
  const currentIndex = this.showMoreButtons.indexOf(currentShowMore)
48
- const currentAll = this.allBlocks[currentIndex]
117
+ const currentAll = this.showAllBlocks[currentIndex]
49
118
  currentAll.classList.remove('hidden')
50
119
  currentShowMore.classList.add('hidden')
51
120
  }
121
+
122
+ getTargetContent(element) {
123
+ const currentIndex = this.buttons.indexOf(element)
124
+ return this.content[currentIndex]
125
+ }
126
+
127
+ setAccordionState(element, state) {
128
+ const targetContent = this.getTargetContent(element)
129
+
130
+ if (state === 'open') {
131
+ element.classList.add('active')
132
+ element.setAttribute('aria-expanded', 'true')
133
+ targetContent.hidden = false
134
+ } else if (state === 'close') {
135
+ element.classList.remove('active')
136
+ element.setAttribute('aria-expanded', 'false')
137
+ targetContent.hidden = true
138
+ }
139
+ }
140
+
141
+ toggleAccordion(e) {
142
+ const { currentTarget } = e
143
+ const targetContent = this.getTargetContent(currentTarget)
144
+ const isHidden = targetContent.hidden
145
+
146
+ if ((isHidden)) {
147
+ this.setAccordionState(currentTarget, 'open')
148
+ } else {
149
+ this.setAccordionState(currentTarget, 'close')
150
+ }
151
+ }
152
+
153
+ trapFocus(element) {
154
+ const firstFocusableEl = this.focusableEls[0]
155
+ const lastFocusableEl = this.focusableEls[this.focusableEls.length - 1]
156
+ const KEYCODE_TAB = 9
157
+
158
+ element.addEventListener('keydown', (e) => {
159
+ const isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB)
160
+
161
+ if (!isTabPressed) { return }
162
+
163
+ if (e.shiftKey) {
164
+ if (document.activeElement === firstFocusableEl) {
165
+ e.preventDefault()
166
+ lastFocusableEl.focus()
167
+ }
168
+ } else if (document.activeElement === lastFocusableEl) {
169
+ e.preventDefault()
170
+ firstFocusableEl.focus()
171
+ }
172
+ })
173
+ }
174
+
175
+ toggleAccept(array) {
176
+ if (this.acceptButton) {
177
+ if (array.length > 0) {
178
+ this.acceptButton.disabled = false
179
+ } else {
180
+ this.acceptButton.disabled = true
181
+ }
182
+ }
183
+ }
184
+
185
+ toggleSelectedState(array) {
186
+ if (array.length > 0) {
187
+ this.openButton.parentElement.classList.add('active')
188
+ } else {
189
+ this.openButton.parentElement.classList.remove('active')
190
+ }
191
+ }
192
+
193
+ resultsCount(array, buttonText) {
194
+ if (this.openButtonText) {
195
+ if (array.length > 0) {
196
+ this.openButtonText.innerText = `${buttonText} (${array.length})`
197
+ } else {
198
+ this.openButtonText.innerText = `${buttonText}`
199
+ }
200
+ }
201
+ }
202
+
203
+ updateDom() {
204
+ this.resultsCount(this.selected, this.buttonLabel)
205
+ this.toggleAccept(this.selected)
206
+ this.toggleSelectedState(this.selected)
207
+ }
208
+
209
+ static getEventType(type) {
210
+ if (type === 'text') {
211
+ return 'input'
212
+ }
213
+ return 'change'
214
+ }
215
+
216
+ static getCondition(element) {
217
+ if (element.type === 'text' || element.type === 'select-one') {
218
+ return element.value !== ''
219
+ }
220
+ return element.checked
221
+ }
222
+
223
+ static singleCount(element, index, id) {
224
+ const isSingleCount = element.closest('.js-filters--single-count')
225
+ if (!isSingleCount) {
226
+ return { uniqueID: `${id}-${index}`, singleID: `${id}-${index}`, isSingleCount }
227
+ }
228
+ return { uniqueID: `${id}`, singleID: `${id}-${index}`, isSingleCount }
229
+ }
230
+
231
+ updateCount(options) {
232
+ const id = uniqueId()
233
+ const GroupArray = []
234
+ if (options.array.length > 0) {
235
+ options.array.forEach((element, index) => {
236
+ const getEventType = this.constructor.getEventType(element.type)
237
+ const { uniqueID, singleID, isSingleCount } = this.constructor.singleCount(element, index, id)
238
+
239
+ if (this.constructor.getCondition(element)) {
240
+ this.selected.push(uniqueID)
241
+ if (isSingleCount) {
242
+ GroupArray.push(singleID)
243
+ }
244
+ this.updateDom()
245
+ }
246
+ element.addEventListener(getEventType, () => {
247
+ const selectedIndex = this.selected.indexOf(uniqueID)
248
+ const singleSelectedIndex = GroupArray.indexOf(singleID)
249
+ if (this.constructor.getCondition(element)) {
250
+ if (!this.selected.includes(uniqueID)) {
251
+ this.selected.push(uniqueID)
252
+ }
253
+ if (isSingleCount && !GroupArray.includes(singleID)) {
254
+ GroupArray.push(singleID)
255
+ }
256
+ this.updateDom()
257
+ } else {
258
+ if (isSingleCount && singleSelectedIndex !== -1) {
259
+ GroupArray.splice(singleSelectedIndex, 1)
260
+ }
261
+ if (!isSingleCount && selectedIndex !== -1) {
262
+ this.selected.splice(selectedIndex, 1)
263
+ } else if (GroupArray.length <= 0) {
264
+ this.selected.splice(selectedIndex, 1)
265
+ }
266
+ this.updateDom()
267
+ }
268
+ })
269
+ })
270
+ }
271
+ }
272
+
273
+ updateStatus(options) {
274
+ const id = uniqueId()
275
+ const text = options.title
276
+ const GroupArray = []
277
+ if (options.array.length > 0) {
278
+ const labelText = (text) ? text.textContent : null
279
+ options.array.forEach((element, index) => {
280
+ const getEventType = this.constructor.getEventType(element.type)
281
+ const { singleID } = this.constructor.singleCount(element, index, id)
282
+ if (this.constructor.getCondition(element)) {
283
+ if (text) {
284
+ text.textContent = labelText
285
+ text.innerHTML = `${text.textContent} ${this.checkIcon}`
286
+ }
287
+ }
288
+ element.addEventListener(getEventType, () => {
289
+ if (this.constructor.getCondition(element)) {
290
+ if (!GroupArray.includes(singleID)) {
291
+ GroupArray.push(singleID)
292
+ }
293
+ } else if (GroupArray.indexOf(singleID) !== -1) {
294
+ GroupArray.splice(GroupArray.indexOf(singleID), 1)
295
+ }
296
+ if (text) {
297
+ if (GroupArray.length > 0) {
298
+ text.textContent = labelText
299
+ text.innerHTML = `${text.textContent} ${this.checkIcon}`
300
+ } else {
301
+ text.textContent = labelText
302
+ }
303
+ }
304
+ })
305
+ })
306
+ }
307
+ }
308
+
309
+ selectedItems() {
310
+ this.filtersItems.forEach((filter) => {
311
+ const button = filter.querySelector('.nsw-filters__item-name')
312
+ const content = filter.querySelector('.nsw-filters__item-content')
313
+ const text = content ? content.querySelectorAll('input[type="text"]') : null
314
+ const selects = content ? content.querySelectorAll('select') : null
315
+ const checkboxes = content ? content.querySelectorAll('input[type="checkbox"]') : null
316
+
317
+ if (!content) return
318
+
319
+ this.updateCount({ array: text, title: button })
320
+ this.updateCount({ array: selects, title: button })
321
+ this.updateCount({ array: checkboxes, title: button })
322
+ this.updateStatus({ array: text, title: button })
323
+ this.updateStatus({ array: selects, title: button })
324
+ this.updateStatus({ array: checkboxes, title: button })
325
+ })
326
+ }
327
+
328
+ clearAllFilters(e) {
329
+ e.preventDefault()
330
+ this.filtersItems.forEach((filter) => {
331
+ const button = filter.querySelector('.nsw-filters__item-name')
332
+ const buttonCheck = button ? button.querySelector('span.nsw-material-icons') : null
333
+ const content = filter.querySelector('.nsw-filters__item-content')
334
+ const text = content.querySelectorAll('input[type="text"]')
335
+ const selects = content.querySelectorAll('select')
336
+ const checkboxes = content.querySelectorAll('input[type="checkbox"]')
337
+ const allFields = [...text, ...selects, ...checkboxes]
338
+
339
+ if (!content) return
340
+
341
+ if (allFields.length > 0) {
342
+ allFields.forEach((input) => {
343
+ const field = input
344
+ if (this.constructor.getCondition(field) && (field.type === 'text' || field.type === 'select-one')) {
345
+ field.value = ''
346
+ } else {
347
+ field.checked = false
348
+ }
349
+ })
350
+ }
351
+
352
+ if (buttonCheck) {
353
+ buttonCheck.remove()
354
+ }
355
+
356
+ this.selected = []
357
+ this.updateDom()
358
+ })
359
+ }
52
360
  }
53
361
 
54
362
  export default Filters