@internetstiftelsen/styleguide 4.0.10 → 4.0.12-beta.0.1

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.
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+
3
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4
+
5
+ var _className = require('./className');
6
+
7
+ var _className2 = _interopRequireDefault(_className);
8
+
9
+ var _htmlTextLength = require('./htmlTextLength');
10
+
11
+ var _htmlTextLength2 = _interopRequireDefault(_htmlTextLength);
12
+
13
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
+
15
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
16
+
17
+ var CharCounter = function () {
18
+ function CharCounter(el) {
19
+ _classCallCheck(this, CharCounter);
20
+
21
+ this.el = el;
22
+ this.counterEl = null;
23
+ this.isRichText = this.el.dataset.richText !== undefined;
24
+ this.min = parseInt(this.el.getAttribute('data-min') || 0, 10);
25
+ this.max = parseInt(this.el.getAttribute('data-max') || 0, 10);
26
+
27
+ if (!this.min && !this.max) {
28
+ console.warn('Either min or max must be set and greater than 0.');
29
+ return;
30
+ }
31
+
32
+ if (this.isRichText) {
33
+ this.waitForEditor();
34
+
35
+ return;
36
+ }
37
+
38
+ this.build();
39
+ this.attach();
40
+ }
41
+
42
+ _createClass(CharCounter, [{
43
+ key: 'waitForEditor',
44
+ value: function waitForEditor() {
45
+ var _this = this;
46
+
47
+ if (this.el.editor) {
48
+ this.build();
49
+ this.attach();
50
+ } else {
51
+ this.el.addEventListener('editor-ready', function () {
52
+ _this.build();
53
+ _this.attach();
54
+ });
55
+ }
56
+ }
57
+ }, {
58
+ key: 'count',
59
+ value: function count() {
60
+ if (this.isRichText) {
61
+ return (0, _htmlTextLength2.default)(this.el.editor.getHTML());
62
+ }
63
+
64
+ return this.el.value.length;
65
+ }
66
+ }, {
67
+ key: 'setCountText',
68
+ value: function setCountText() {
69
+ var count = this.count();
70
+
71
+ if (this.min && count < this.min) {
72
+ this.counterEl.textContent = count + '/' + this.min;
73
+ this.counterEl.className = 'color-ruby ' + (0, _className2.default)('a-meta');
74
+
75
+ return;
76
+ }
77
+
78
+ if (this.max && count > this.max) {
79
+ this.counterEl.textContent = count + '/' + this.max;
80
+ this.counterEl.classList.remove('color-granit');
81
+ this.counterEl.className = 'color-ruby ' + (0, _className2.default)('a-meta');
82
+
83
+ return;
84
+ }
85
+
86
+ this.counterEl.textContent = count + '/' + (this.max || this.min);
87
+ this.counterEl.className = 'color-jade ' + (0, _className2.default)('a-meta');
88
+ }
89
+ }, {
90
+ key: 'build',
91
+ value: function build() {
92
+ var counter = document.createElement('small');
93
+ var wrapper = void 0;
94
+
95
+ if (this.isRichText) {
96
+ wrapper = this.el.editor.options.element;
97
+ wrapper.style.paddingRight = '3.8333333333rem';
98
+ } else {
99
+ wrapper = document.createElement('div');
100
+
101
+ wrapper.className = 'u-position-relative';
102
+ this.el.parentNode.insertBefore(wrapper, this.el);
103
+ wrapper.appendChild(this.el);
104
+
105
+ this.el.style.paddingRight = '3.8333333333rem';
106
+ }
107
+
108
+ counter.className = 'color-granit ' + (0, _className2.default)('a-meta');
109
+ counter.style.cssText = 'position: absolute; top: 5px; right: 10px; z-index: 501;';
110
+
111
+ wrapper.appendChild(counter);
112
+
113
+ this.counterEl = counter;
114
+ this.setCountText();
115
+ }
116
+ }, {
117
+ key: 'attach',
118
+ value: function attach() {
119
+ var _this2 = this;
120
+
121
+ if (this.isRichText) {
122
+ this.el.editor.on('update', function () {
123
+ _this2.setCountText();
124
+ });
125
+ } else {
126
+ this.el.addEventListener('input', function () {
127
+ _this2.setCountText();
128
+ });
129
+ }
130
+ }
131
+ }]);
132
+
133
+ return CharCounter;
134
+ }();
135
+
136
+ var elements = document.querySelectorAll('[data-min], [data-max]');
137
+ elements.forEach(function (el) {
138
+ el.charCounter = new CharCounter(el);
139
+ });
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = htmlTextLength;
7
+ function htmlTextLength(html) {
8
+ var div = document.createElement('div');
9
+
10
+ div.innerHTML = html;
11
+
12
+ return div.textContent.length;
13
+ }
@@ -14,8 +14,10 @@ function toggleTextOnClick(e) {
14
14
  target.innerText = options[nextIteration];
15
15
  }
16
16
 
17
- var toggleTextButton = document.querySelector('[data-toggle-text]');
18
-
19
- if (toggleTextButton) {
20
- toggleTextButton.addEventListener('click', toggleTextOnClick);
21
- }
17
+ /* eslint-disable */
18
+ document.addEventListener('click', function (e) {
19
+ if (e.target.closest('[data-toggle-text]')) {
20
+ toggleTextOnClick(e);
21
+ return false;
22
+ }
23
+ });
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ // Show/hide target container when Radiobutton is selected
4
+ var radioButtons = document.querySelectorAll('[data-toggle="radio"]');
5
+
6
+ // Hide all target containers on page load
7
+ radioButtons.forEach(function (radio) {
8
+ var targetId = radio.getAttribute('data-target');
9
+ var targetContainer = document.getElementById(targetId);
10
+ if (targetContainer) {
11
+ targetContainer.setAttribute('aria-hidden', 'true');
12
+ }
13
+ });
14
+
15
+ // Function to toggle visibility
16
+ function toggleVisibility(event) {
17
+ // Hide all target containers
18
+ radioButtons.forEach(function (radio) {
19
+ var targetId = radio.getAttribute('data-target');
20
+ var targetContainer = document.getElementById(targetId);
21
+ if (targetContainer) {
22
+ targetContainer.setAttribute('aria-hidden', 'true');
23
+ }
24
+
25
+ // Update aria-expanded for all radio buttons
26
+ radio.setAttribute('aria-expanded', radio.checked ? 'true' : 'false');
27
+ });
28
+
29
+ // Show the selected target container
30
+ var selectedTargetId = event.target.getAttribute('data-target');
31
+ var selectedTargetContainer = document.getElementById(selectedTargetId);
32
+ if (selectedTargetContainer) {
33
+ selectedTargetContainer.setAttribute('aria-hidden', 'false');
34
+ }
35
+ }
36
+
37
+ // Add event listener to radio buttons
38
+ radioButtons.forEach(function (radio) {
39
+ radio.addEventListener('change', toggleVisibility);
40
+ });
@@ -96,6 +96,9 @@ function createCover(el) {
96
96
 
97
97
  img.loading = 'lazy';
98
98
  img.src = url;
99
+ img.alt = 'tumnagel för video';
100
+ img.width = 480;
101
+ img.height = 270;
99
102
  }
100
103
 
101
104
  function setupYoutubePlayer(el) {
@@ -203,6 +203,14 @@ function setupTextArea(el) {
203
203
  el.parentNode.insertBefore(editorEl, el);
204
204
 
205
205
  createToolbar(editorEl, editor);
206
+
207
+ var event = new CustomEvent('editor-ready', {
208
+ detail: {
209
+ editor: editor
210
+ }
211
+ });
212
+
213
+ el.dispatchEvent(event);
206
214
  }
207
215
 
208
216
  function init() {
@@ -64,4 +64,12 @@ require('./organisms/schedule/schedule-filter');
64
64
 
65
65
  require('./assets/js/ot');
66
66
 
67
- require('./atoms/range/range');
67
+ require('./assets/js/charCounter');
68
+
69
+ require('./atoms/range/range');
70
+
71
+ require('./assets/js/utmGenerator');
72
+
73
+ require('./assets/js/textToggle');
74
+
75
+ require('./molecules/multi-select/multi-select');
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ /* eslint-disable */
4
+ var className = 'm-multi-select';
5
+ var multiSelectElements = document.querySelectorAll('.js-' + className);
6
+ var namespace = void 0;
7
+
8
+ if (multiSelectElements) {
9
+ namespace = getComputedStyle(multiSelectElements[0], ':before').content.replace(/["']/g, '');
10
+ }
11
+
12
+ var currentFocus = -1; // Tracks the currently focused item in the suggestions
13
+
14
+ // Highlight the active (focused) suggestion
15
+ function setActive(items) {
16
+ if (!items.length) return false;
17
+ removeActive(items);
18
+ if (currentFocus >= items.length) currentFocus = 0;
19
+ if (currentFocus < 0) currentFocus = items.length - 1;
20
+ items[currentFocus].classList.add('autocomplete-active');
21
+
22
+ return items;
23
+ }
24
+
25
+ // Remove highlighting from all suggestions
26
+ function removeActive(items) {
27
+ for (var i = 0; i < items.length; i += 1) {
28
+ items[i].classList.remove('autocomplete-active');
29
+ }
30
+ }
31
+
32
+ // Add a selected item to the list of selected items
33
+ function addSelectedItem(item) {
34
+ var selectedItemsList = document.querySelector('.js-m-multi-select-selected-items');
35
+ var newItem = document.createElement('li');
36
+ newItem.textContent = item + ' ';
37
+ newItem.classList.add(namespace + 'a-tag');
38
+ newItem.classList.add(namespace + 'm-multi-select__tag');
39
+
40
+ var removeBtn = document.createElement('button');
41
+ removeBtn.classList.add(namespace + 'm-multi-select-selected-items__remove-btn');
42
+
43
+ var buttonTextContainer = document.createElement('span');
44
+ buttonTextContainer.classList.add('u-visuallyhidden');
45
+ removeBtn.appendChild(buttonTextContainer);
46
+ buttonTextContainer.textContent = 'Ta bort ' + item; // Accessibility label for screen readers
47
+
48
+ // Event listener for removing the selected item
49
+ removeBtn.addEventListener('click', function () {
50
+ removeItem(newItem, Array.from(selectedItemsList.children).indexOf(newItem));
51
+ });
52
+ newItem.appendChild(removeBtn);
53
+ selectedItemsList.appendChild(newItem);
54
+ }
55
+
56
+ // Remove an item and manage focus appropriately
57
+ function removeItem(item, index) {
58
+ var selectedItemsList = document.querySelector('.js-m-multi-select-selected-items');
59
+ selectedItemsList.removeChild(item);
60
+
61
+ var remainingItems = selectedItemsList.getElementsByTagName('div');
62
+ // Focus management: set focus to the next item, or the search input if no items left
63
+ if (remainingItems.length > 0) {
64
+ if (index < remainingItems.length) {
65
+ remainingItems[index].getElementsByTagName('button')[0].focus();
66
+ } else {
67
+ remainingItems[remainingItems.length - 1].getElementsByTagName('button')[0].focus();
68
+ }
69
+ } else {
70
+ multiSelectInput.focus();
71
+ }
72
+ }
73
+
74
+ function setup(multiSelectElement) {
75
+ var multiSelectInput = multiSelectElement.querySelector('.js-' + className + '__input');
76
+ var suggestionsBox = multiSelectElement.querySelector('.js-' + className + '-suggestions-box');
77
+ var suggestionsData = multiSelectInput.getAttribute('data-multi-select-suggestions');
78
+
79
+ // Event listener for input changes in the search field
80
+ multiSelectInput.addEventListener('input', function () {
81
+ var value = this.value;
82
+ // Clear suggestions if less than 2 characters are typed
83
+ if (value.length < 2) {
84
+ suggestionsBox.innerHTML = '';
85
+ return;
86
+ }
87
+
88
+ // Define JSON
89
+ var suggestions = document.getElementById(suggestionsData).textContent;
90
+ suggestions = JSON.parse(suggestions);
91
+
92
+ // Filter suggestions based on the input value
93
+ var filtered = suggestions.filter(function (item) {
94
+ return item.name.toLowerCase().startsWith(value.toLowerCase());
95
+ });
96
+ // Populate the suggestions box with the filtered results
97
+ suggestionsBox.innerHTML = filtered.map(function (item) {
98
+ return '<button class=\'' + namespace + 'm-multi-select__suggestion-btn\' tabindex=\'0\'>' + item.name + '</button>';
99
+ }).join('');
100
+ // Reset the current focus
101
+ currentFocus = -1;
102
+ });
103
+
104
+ // Event listener for keydown events for navigation and selection in the suggestions box
105
+ multiSelectInput.addEventListener('keydown', function (e) {
106
+ var items = suggestionsBox.getElementsByClassName(namespace + 'm-multi-select__suggestion-btn');
107
+ // Navigate down in the suggestions list
108
+ if (e.keyCode == 40) {
109
+ currentFocus = (currentFocus + 1) % items.length;
110
+ setActive(items);
111
+ // Navigate up in the suggestions list
112
+ } else if (e.keyCode == 38) {
113
+ currentFocus = (currentFocus - 1 + items.length) % items.length;
114
+ setActive(items);
115
+ // Handle Enter key to select a focused item
116
+ } else if (e.keyCode == 13) {
117
+ e.preventDefault();
118
+ if (currentFocus > -1 && items[currentFocus]) {
119
+ addSelectedItem(items[currentFocus].textContent);
120
+ suggestionsBox.innerHTML = '';
121
+ multiSelectInput.value = '';
122
+ currentFocus = -1;
123
+ }
124
+ }
125
+ });
126
+
127
+ // Event listener for the suggestions box
128
+ suggestionsBox.addEventListener('click', function (e) {
129
+ // Add the clicked suggestion to the selected items list
130
+ if (e.target && e.target.classList.contains('suggestion-btn')) {
131
+ addSelectedItem(e.target.textContent);
132
+ suggestionsBox.innerHTML = '';
133
+ multiSelectInput.value = '';
134
+ }
135
+ });
136
+ }
137
+
138
+ if (multiSelectElements) {
139
+ [].forEach.call(multiSelectElements, setup);
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetstiftelsen/styleguide",
3
- "version": "4.0.10",
3
+ "version": "4.0.12-beta.0.1",
4
4
  "main": "dist/components.js",
5
5
  "ports": {
6
6
  "fractal": "3000"
package/src/app.scss CHANGED
@@ -68,6 +68,7 @@ $namespace: '';
68
68
  @import 'molecules/glider/glider-hero';
69
69
  @import 'molecules/continue-video-guide/continue-video-guide';
70
70
  @import 'molecules/overview-navigation/overview-navigation';
71
+ @import 'molecules/multi-select/multi-select';
71
72
 
72
73
  // Organisms
73
74
  @import 'organisms/header/header';
@@ -12,8 +12,10 @@ function toggleTextOnClick(e) {
12
12
  target.innerText = options[nextIteration];
13
13
  }
14
14
 
15
- const toggleTextButton = document.querySelector('[data-toggle-text]');
16
-
17
- if (toggleTextButton) {
18
- toggleTextButton.addEventListener('click', toggleTextOnClick);
19
- }
15
+ /* eslint-disable */
16
+ document.addEventListener('click', (e) => {
17
+ if (e.target.closest('[data-toggle-text]')) {
18
+ toggleTextOnClick(e);
19
+ return false;
20
+ }
21
+ });
@@ -0,0 +1,38 @@
1
+ // Show/hide target container when Radiobutton is selected
2
+ const radioButtons = document.querySelectorAll('[data-toggle="radio"]');
3
+
4
+ // Hide all target containers on page load
5
+ radioButtons.forEach((radio) => {
6
+ const targetId = radio.getAttribute('data-target');
7
+ const targetContainer = document.getElementById(targetId);
8
+ if (targetContainer) {
9
+ targetContainer.setAttribute('aria-hidden', 'true');
10
+ }
11
+ });
12
+
13
+ // Function to toggle visibility
14
+ function toggleVisibility(event) {
15
+ // Hide all target containers
16
+ radioButtons.forEach((radio) => {
17
+ const targetId = radio.getAttribute('data-target');
18
+ const targetContainer = document.getElementById(targetId);
19
+ if (targetContainer) {
20
+ targetContainer.setAttribute('aria-hidden', 'true');
21
+ }
22
+
23
+ // Update aria-expanded for all radio buttons
24
+ radio.setAttribute('aria-expanded', radio.checked ? 'true' : 'false');
25
+ });
26
+
27
+ // Show the selected target container
28
+ const selectedTargetId = event.target.getAttribute('data-target');
29
+ const selectedTargetContainer = document.getElementById(selectedTargetId);
30
+ if (selectedTargetContainer) {
31
+ selectedTargetContainer.setAttribute('aria-hidden', 'false');
32
+ }
33
+ }
34
+
35
+ // Add event listener to radio buttons
36
+ radioButtons.forEach((radio) => {
37
+ radio.addEventListener('change', toggleVisibility);
38
+ });
@@ -81,6 +81,9 @@ function createCover(el) {
81
81
 
82
82
  img.loading = 'lazy';
83
83
  img.src = url;
84
+ img.alt = 'tumnagel för video';
85
+ img.width = 480;
86
+ img.height = 270;
84
87
  }
85
88
 
86
89
  function setupYoutubePlayer(el) {
package/src/components.js CHANGED
@@ -32,3 +32,6 @@ import './organisms/schedule/schedule-filter';
32
32
  import './assets/js/ot';
33
33
  import './assets/js/charCounter';
34
34
  import './atoms/range/range';
35
+ import './assets/js/utmGenerator';
36
+ import './assets/js/textToggle';
37
+ import './molecules/multi-select/multi-select';
@@ -0,0 +1,108 @@
1
+ @charset "UTF-8";
2
+
3
+ @include molecule(multi-select) {
4
+ position: relative;
5
+
6
+ &::before {
7
+ @extend %u-visuallyhidden;
8
+
9
+ content: $namespace;
10
+ }
11
+
12
+ @include e(suggestions-box) {
13
+ position: absolute;
14
+ border-top: none;
15
+ z-index: z_index(foreground);
16
+ top: 100%;
17
+ left: 0;
18
+ right: 0;
19
+ background-color: $color-snow;
20
+ overflow-y: auto;
21
+ max-height: rhythm(15);
22
+ border-bottom-left-radius: $border-radius;
23
+ border-bottom-right-radius: $border-radius;
24
+
25
+ @extend %box-shadow;
26
+ }
27
+
28
+ @include e(suggestion-btn) {
29
+ padding: rhythm(1);
30
+ cursor: pointer;
31
+ background-color: $color-snow;
32
+ border: none;
33
+ border-bottom: 1px solid $color-concrete;
34
+ width: 100%;
35
+ text-align: left;
36
+
37
+ &:hover,
38
+ &.autocomplete-active {
39
+ background-color: $color-ocean-dark;
40
+ color: $color-snow;
41
+ }
42
+ }
43
+
44
+ @include e(tag){
45
+ margin-bottom: rhythm(1);
46
+ background-color: $color-ash;
47
+ text-transform: none;
48
+ font-size: $size-medium;
49
+
50
+ &:hover,
51
+ &:focus {
52
+ background-color: $color-ash;
53
+ color: $color-cyberspace;
54
+ }
55
+ }
56
+ }
57
+
58
+ /* Selected items container */
59
+ @include molecule(multi-select-selected-items) {
60
+ margin-top: -#{rhythm(1)};
61
+ padding: rhythm(2) rhythm(1) 0 rhythm(1);
62
+ border: 1px solid #d4d4d4;
63
+ background-color: $color-concrete;
64
+ border-bottom-left-radius: $border-radius;
65
+ border-bottom-right-radius: $border-radius;
66
+
67
+ &:empty {
68
+ display: none;
69
+ }
70
+
71
+ @include e(remove-btn) {
72
+ margin-left: 5px;
73
+ border: none;
74
+ background-color: #d80000;
75
+ color: white;
76
+ border-radius: 50%;
77
+ cursor: pointer;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ line-height: 1;
82
+ width: $icon-size-small;
83
+ height: $icon-size-small;
84
+ position: relative;
85
+ transform: translateX(rhythm(0.5));
86
+
87
+ &::after {
88
+ content: '';
89
+ display: block;
90
+ width: 100%;
91
+ height: 100%;
92
+ position: absolute;
93
+ top: 0;
94
+ right: 0;
95
+ bottom: 0;
96
+ left: 0;
97
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI4LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI4LDEwIDIsMTYgMCwxNCA2LDggMCwyIDIsMCA4LDYgMTQsMCAxNiwyIDEwLDggMTYsMTQgMTQsMTYgIi8+Cjwvc3ZnPgo=);
98
+ background-repeat: no-repeat;
99
+ background-position: center center;
100
+ background-size: calc($icon-size-small / 2) calc($icon-size-small / 2);
101
+ }
102
+
103
+ &:hover,
104
+ &:focus {
105
+ background-color: $color-cyberspace;
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,134 @@
1
+ /* eslint-disable */
2
+ const className = 'm-multi-select';
3
+ const multiSelectElements = document.querySelectorAll(`.js-${className}`);
4
+ let namespace;
5
+
6
+ if( multiSelectElements ) {
7
+ namespace = getComputedStyle(multiSelectElements[0], ':before').content.replace(/["']/g, '');
8
+ }
9
+
10
+ let currentFocus = -1; // Tracks the currently focused item in the suggestions
11
+
12
+ // Highlight the active (focused) suggestion
13
+ function setActive(items) {
14
+ if (!items.length) return false;
15
+ removeActive(items);
16
+ if (currentFocus >= items.length) currentFocus = 0;
17
+ if (currentFocus < 0) currentFocus = items.length - 1;
18
+ items[currentFocus].classList.add('autocomplete-active');
19
+
20
+ return items;
21
+ }
22
+
23
+ // Remove highlighting from all suggestions
24
+ function removeActive(items) {
25
+ for (let i = 0; i < items.length; i+=1) {
26
+ items[i].classList.remove('autocomplete-active');
27
+ }
28
+ }
29
+
30
+ // Add a selected item to the list of selected items
31
+ function addSelectedItem(item) {
32
+ const selectedItemsList = document.querySelector('.js-m-multi-select-selected-items');
33
+ const newItem = document.createElement('li');
34
+ newItem.textContent = item + ' ';
35
+ newItem.classList.add(`${namespace}a-tag`);
36
+ newItem.classList.add(`${namespace}m-multi-select__tag`);
37
+
38
+ const removeBtn = document.createElement('button');
39
+ removeBtn.classList.add(`${namespace}m-multi-select-selected-items__remove-btn`);
40
+
41
+ const buttonTextContainer = document.createElement('span');
42
+ buttonTextContainer.classList.add('u-visuallyhidden');
43
+ removeBtn.appendChild(buttonTextContainer);
44
+ buttonTextContainer.textContent ='Ta bort ' + item; // Accessibility label for screen readers
45
+
46
+ // Event listener for removing the selected item
47
+ removeBtn.addEventListener('click', function () {
48
+ removeItem(newItem, Array.from(selectedItemsList.children).indexOf(newItem));
49
+ });
50
+ newItem.appendChild(removeBtn);
51
+ selectedItemsList.appendChild(newItem);
52
+ }
53
+
54
+ // Remove an item and manage focus appropriately
55
+ function removeItem(item, index) {
56
+ const selectedItemsList = document.querySelector('.js-m-multi-select-selected-items');
57
+ selectedItemsList.removeChild(item);
58
+
59
+ let remainingItems = selectedItemsList.getElementsByTagName('div');
60
+ // Focus management: set focus to the next item, or the search input if no items left
61
+ if (remainingItems.length > 0) {
62
+ if (index < remainingItems.length) {
63
+ remainingItems[index].getElementsByTagName('button')[0].focus();
64
+ } else {
65
+ remainingItems[remainingItems.length - 1].getElementsByTagName('button')[0].focus();
66
+ }
67
+ } else {
68
+ multiSelectInput.focus();
69
+ }
70
+ }
71
+
72
+ function setup(multiSelectElement) {
73
+ const multiSelectInput = multiSelectElement.querySelector(`.js-${className}__input`);
74
+ const suggestionsBox = multiSelectElement.querySelector(`.js-${className}-suggestions-box`);
75
+ const suggestionsData = multiSelectInput.getAttribute('data-multi-select-suggestions');
76
+
77
+ // Event listener for input changes in the search field
78
+ multiSelectInput.addEventListener('input', function () {
79
+ const value = this.value;
80
+ // Clear suggestions if less than 2 characters are typed
81
+ if (value.length < 2) {
82
+ suggestionsBox.innerHTML = '';
83
+ return;
84
+ }
85
+
86
+ // Define JSON
87
+ let suggestions = document.getElementById(suggestionsData).textContent;
88
+ suggestions = JSON.parse(suggestions);
89
+
90
+ // Filter suggestions based on the input value
91
+ const filtered = suggestions.filter(item => item.name.toLowerCase().startsWith(value.toLowerCase()));
92
+ // Populate the suggestions box with the filtered results
93
+ suggestionsBox.innerHTML = filtered.map(item => `<button class='${namespace}m-multi-select__suggestion-btn' tabindex='0'>${item.name}</button>`).join('');
94
+ // Reset the current focus
95
+ currentFocus = -1;
96
+ });
97
+
98
+ // Event listener for keydown events for navigation and selection in the suggestions box
99
+ multiSelectInput.addEventListener('keydown', function (e) {
100
+ let items = suggestionsBox.getElementsByClassName(`${namespace}m-multi-select__suggestion-btn`);
101
+ // Navigate down in the suggestions list
102
+ if (e.keyCode == 40) {
103
+ currentFocus = (currentFocus + 1) % items.length;
104
+ setActive(items);
105
+ // Navigate up in the suggestions list
106
+ } else if (e.keyCode == 38) {
107
+ currentFocus = (currentFocus - 1 + items.length) % items.length;
108
+ setActive(items);
109
+ // Handle Enter key to select a focused item
110
+ } else if (e.keyCode == 13) {
111
+ e.preventDefault();
112
+ if (currentFocus > -1 && items[currentFocus]) {
113
+ addSelectedItem(items[currentFocus].textContent);
114
+ suggestionsBox.innerHTML = '';
115
+ multiSelectInput.value = '';
116
+ currentFocus = -1;
117
+ }
118
+ }
119
+ });
120
+
121
+ // Event listener for the suggestions box
122
+ suggestionsBox.addEventListener('click', function (e) {
123
+ // Add the clicked suggestion to the selected items list
124
+ if (e.target && e.target.classList.contains('suggestion-btn')) {
125
+ addSelectedItem(e.target.textContent);
126
+ suggestionsBox.innerHTML = '';
127
+ multiSelectInput.value = '';
128
+ }
129
+ });
130
+ }
131
+
132
+ if (multiSelectElements) {
133
+ [].forEach.call(multiSelectElements, setup);
134
+ }
@@ -0,0 +1,2 @@
1
+ # Accessible Multi Select with Autocomplete
2
+ Use your mouse or keyboard to select and remove items. Compatible with screen readers.
@@ -107,3 +107,7 @@
107
107
  display: none !important;
108
108
  }
109
109
  }
110
+
111
+ .u-aria-hidden[aria-hidden='true'] {
112
+ display: none !important;
113
+ }