@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.
- package/dist/assets/js/charCounter.js +139 -0
- package/dist/assets/js/htmlTextLength.js +13 -0
- package/dist/assets/js/textToggle.js +7 -5
- package/dist/assets/js/utmGenerator.js +40 -0
- package/dist/assets/js/youtube.js +3 -0
- package/dist/atoms/textarea/rich-text.js +8 -0
- package/dist/components.js +9 -1
- package/dist/molecules/multi-select/multi-select.js +140 -0
- package/package.json +1 -1
- package/src/app.scss +1 -0
- package/src/assets/js/textToggle.js +7 -5
- package/src/assets/js/utmGenerator.js +38 -0
- package/src/assets/js/youtube.js +3 -0
- package/src/components.js +3 -0
- package/src/molecules/multi-select/_multi-select.scss +108 -0
- package/src/molecules/multi-select/multi-select.js +134 -0
- package/src/molecules/multi-select/readme.md +2 -0
- package/src/utilities/_hide.scss +4 -0
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
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
|
+
});
|
|
@@ -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() {
|
package/dist/components.js
CHANGED
|
@@ -64,4 +64,12 @@ require('./organisms/schedule/schedule-filter');
|
|
|
64
64
|
|
|
65
65
|
require('./assets/js/ot');
|
|
66
66
|
|
|
67
|
-
require('./
|
|
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
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
|
-
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
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
|
+
});
|
package/src/assets/js/youtube.js
CHANGED
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
|
+
}
|