@internetstiftelsen/styleguide 4.0.10-beta.0.1 → 4.0.11

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]');
17
+ var toggleTextButtons = document.querySelectorAll('[data-toggle-text]');
18
18
 
19
- if (toggleTextButton) {
20
- toggleTextButton.addEventListener('click', toggleTextOnClick);
19
+ if (toggleTextButtons) {
20
+ [].forEach.call(toggleTextButtons, function (toggleTextButton) {
21
+ toggleTextButton.addEventListener('click', toggleTextOnClick);
22
+ });
21
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,132 @@
1
+ 'use strict';
2
+
3
+ /* eslint-disable */
4
+ // Get references to the search input and suggestions box elements
5
+ var className = 'm-multi-select';
6
+ var multiSelectElement = document.querySelector('.js-' + className);
7
+ var multiSelectInput = document.querySelector('.js-' + className + '__input');
8
+ var suggestionsBox = document.querySelector('.js-' + className + '-suggestions-box');
9
+
10
+ if (multiSelectInput && suggestionsBox) {
11
+
12
+ // Function to highlight the active (focused) suggestion
13
+ var setActive = 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
+ // Function to remove highlighting from all suggestions
24
+
25
+
26
+ var removeActive = function removeActive(items) {
27
+ for (var i = 0; i < items.length; i++) {
28
+ items[i].classList.remove('autocomplete-active');
29
+ }
30
+ };
31
+
32
+ // Function to add a selected item to the list of selected items
33
+
34
+
35
+ var addSelectedItem = function addSelectedItem(item) {
36
+ var container = document.getElementById('selectedItemsContainer');
37
+ var newItem = document.createElement('li');
38
+ newItem.textContent = item + ' ';
39
+ var removeBtn = document.createElement('button');
40
+ var buttonTextContainer = document.createElement('span');
41
+ //removeBtn.textContent = 'x';
42
+ buttonTextContainer.classList.add('visually-hidden');
43
+ removeBtn.appendChild(buttonTextContainer);
44
+ buttonTextContainer.textContent = 'Remove ' + item; // Accessibility label for screen readers
45
+ // Event listener for removing the selected item
46
+ removeBtn.addEventListener('click', function () {
47
+ removeItem(newItem, Array.from(container.children).indexOf(newItem));
48
+ });
49
+ newItem.appendChild(removeBtn);
50
+ container.appendChild(newItem);
51
+ };
52
+
53
+ // Function to remove an item and manage focus appropriately
54
+
55
+
56
+ var removeItem = function removeItem(item, index) {
57
+ var container = document.getElementById('selectedItemsContainer');
58
+ container.removeChild(item);
59
+
60
+ var remainingItems = container.getElementsByTagName('div');
61
+ // Focus management: set focus to the next item, or the search input if no items left
62
+ if (remainingItems.length > 0) {
63
+ if (index < remainingItems.length) {
64
+ remainingItems[index].getElementsByTagName('button')[0].focus();
65
+ } else {
66
+ remainingItems[remainingItems.length - 1].getElementsByTagName('button')[0].focus();
67
+ }
68
+ } else {
69
+ multiSelectInput.focus();
70
+ }
71
+ };
72
+
73
+ // Event listener for input changes in the search field
74
+
75
+
76
+ var currentFocus = -1; // Tracks the currently focused item in the suggestions
77
+ var namespace = getComputedStyle(multiSelectElement, ':before').content.replace(/["']/g, '');multiSelectInput.addEventListener('input', function () {
78
+ var value = this.value;
79
+ // Clear suggestions if less than 2 characters are typed
80
+ if (value.length < 2) {
81
+ suggestionsBox.innerHTML = '';
82
+ return;
83
+ }
84
+
85
+ // Define a JSON array of 100 real cities
86
+ var suggestions = [{ name: 'New York' }, { name: 'Los Angeles' }, { name: 'Chicago' }, { name: 'Houston' }, { name: 'Phoenix' }, { name: 'Philadelphia' }, { name: 'San Antonio' }, { name: 'San Diego' }, { name: 'Dallas' }, { name: 'San Jose' }, { name: 'Austin' }, { name: 'Jacksonville' }, { name: 'Fort Worth' }, { name: 'Columbus' }, { name: 'San Francisco' }, { name: 'Tokyo' }, { name: 'Delhi' }, { name: 'Shanghai' }, { name: 'Sao Paulo' }, { name: 'Mumbai' }, { name: 'Beijing' }, { name: 'Cairo' }, { name: 'Dhaka' }, { name: 'Mexico City' }, { name: 'Osaka' }, { name: 'Karachi' }, { name: 'Chongqing' }, { name: 'Istanbul' }, { name: 'Buenos Aires' }, { name: 'Kolkata' }, { name: 'Kinshasa' }, { name: 'Lagos' }, { name: 'Manila' }, { name: 'Tianjin' }, { name: 'Rio de Janeiro' }, { name: 'Guangzhou' }, { name: 'Lahore' }, { name: 'Moscow' }, { name: 'Shenzhen' }, { name: 'Bangalore' }, { name: 'Paris' }, { name: 'Bogota' }, { name: 'Jakarta' }, { name: 'Chennai' }, { name: 'Lima' }, { name: 'Bangkok' }, { name: 'Seoul' }, { name: 'Nagoya' }, { name: 'Hyderabad' }, { name: 'London' }, { name: 'Tehran' }, { name: 'Chengdu' }, { name: 'Nanjing' }, { name: 'Wuhan' }, { name: 'Ho Chi Minh City' }, { name: 'Luanda' }, { name: 'Ahmedabad' }, { name: 'Kuala Lumpur' }, { name: 'Xi’an' }, { name: 'Hong Kong' }, { name: 'Dongguan' }, { name: 'Hangzhou' }, { name: 'Foshan' }, { name: 'Shenyang' }, { name: 'Riyadh' }, { name: 'Baghdad' }, { name: 'Santiago' }, { name: 'Surat' }, { name: 'Madrid' }, { name: 'Suzhou' }, { name: 'Pune' }, { name: 'Harbin' }, { name: 'Houston' }, { name: 'Toronto' }, { name: 'Dar es Salaam' }, { name: 'Miami' }, { name: 'Belo Horizonte' }, { name: 'Singapore' }, { name: 'Atlanta' }, { name: 'Fukuoka' }, { name: 'Khartoum' }, { name: 'Barcelona' }, { name: 'Johannesburg' }, { name: 'Saint Petersburg' }, { name: 'Qingdao' }, { name: 'Dalian' }, { name: 'Washington, D.C.' }, { name: 'Yangon' }, { name: 'Alexandria' }, { name: 'Jinan' }, { name: 'Guadalajara' }, { name: 'Sydney' }, { name: 'Melbourne' }, { name: 'Montreal' }, { name: 'Ankara' }, { name: 'Recife' }, { name: 'Durban' }, { name: 'Porto Alegre' }, { name: 'Dusseldorf' }, { name: 'Hamburg' }, { name: 'Cape Town' }];
87
+
88
+ // Filter suggestions based on the input value
89
+ var filtered = suggestions.filter(function (item) {
90
+ return item.name.toLowerCase().startsWith(value.toLowerCase());
91
+ });
92
+ // Populate the suggestions box with the filtered results
93
+ suggestionsBox.innerHTML = filtered.map(function (item) {
94
+ return '<button class=\'' + namespace + 'm-multi-select__suggestion-btn\' tabindex=\'0\'>' + item.name + '</button>';
95
+ }).join('');
96
+ // Reset the current focus
97
+ currentFocus = -1;
98
+ });
99
+
100
+ // Event listener for keydown events for navigation and selection in the suggestions box
101
+ multiSelectInput.addEventListener('keydown', function (e) {
102
+ var items = suggestionsBox.getElementsByClassName(namespace + 'm-multi-select__suggestion-btn');
103
+ // Navigate down in the suggestions list
104
+ if (e.keyCode == 40) {
105
+ currentFocus = (currentFocus + 1) % items.length;
106
+ setActive(items);
107
+ // Navigate up in the suggestions list
108
+ } else if (e.keyCode == 38) {
109
+ currentFocus = (currentFocus - 1 + items.length) % items.length;
110
+ setActive(items);
111
+ // Handle Enter key to select a focused item
112
+ } else if (e.keyCode == 13) {
113
+ e.preventDefault();
114
+ if (currentFocus > -1 && items[currentFocus]) {
115
+ addSelectedItem(items[currentFocus].textContent);
116
+ suggestionsBox.innerHTML = '';
117
+ multiSelectInput.value = '';
118
+ currentFocus = -1;
119
+ }
120
+ }
121
+ });
122
+
123
+ // Click event listener for the suggestions box
124
+ suggestionsBox.addEventListener('click', function (e) {
125
+ // Add the clicked suggestion to the selected items list
126
+ if (e.target && e.target.classList.contains('suggestion-btn')) {
127
+ addSelectedItem(e.target.textContent);
128
+ suggestionsBox.innerHTML = '';
129
+ multiSelectInput.value = '';
130
+ }
131
+ });
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetstiftelsen/styleguide",
3
- "version": "4.0.10-beta.0.1",
3
+ "version": "4.0.11",
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';
@@ -0,0 +1,111 @@
1
+ import className from './className';
2
+ import htmlTextLength from './htmlTextLength';
3
+
4
+ class CharCounter {
5
+ constructor(el) {
6
+ this.el = el;
7
+ this.counterEl = null;
8
+ this.isRichText = this.el.dataset.richText !== undefined;
9
+ this.min = parseInt(this.el.getAttribute('data-min') || 0, 10);
10
+ this.max = parseInt(this.el.getAttribute('data-max') || 0, 10);
11
+
12
+ if (!this.min && !this.max) {
13
+ console.warn('Either min or max must be set and greater than 0.');
14
+ return;
15
+ }
16
+
17
+ if (this.isRichText) {
18
+ this.waitForEditor();
19
+
20
+ return;
21
+ }
22
+
23
+ this.build();
24
+ this.attach();
25
+ }
26
+
27
+ waitForEditor() {
28
+ if (this.el.editor) {
29
+ this.build();
30
+ this.attach();
31
+ } else {
32
+ this.el.addEventListener('editor-ready', () => {
33
+ this.build();
34
+ this.attach();
35
+ });
36
+ }
37
+ }
38
+
39
+ count() {
40
+ if (this.isRichText) {
41
+ return htmlTextLength(this.el.editor.getHTML());
42
+ }
43
+
44
+ return this.el.value.length;
45
+ }
46
+
47
+ setCountText() {
48
+ const count = this.count();
49
+
50
+ if (this.min && count < this.min) {
51
+ this.counterEl.textContent = `${count}/${this.min}`;
52
+ this.counterEl.className = `color-ruby ${className('a-meta')}`;
53
+
54
+ return;
55
+ }
56
+
57
+ if (this.max && count > this.max) {
58
+ this.counterEl.textContent = `${count}/${this.max}`;
59
+ this.counterEl.classList.remove('color-granit');
60
+ this.counterEl.className = `color-ruby ${className('a-meta')}`;
61
+
62
+ return;
63
+ }
64
+
65
+ this.counterEl.textContent = `${count}/${this.max || this.min}`;
66
+ this.counterEl.className = `color-jade ${className('a-meta')}`;
67
+ }
68
+
69
+ build() {
70
+ const counter = document.createElement('small');
71
+ let wrapper;
72
+
73
+ if (this.isRichText) {
74
+ wrapper = this.el.editor.options.element;
75
+ wrapper.style.paddingRight = '3.8333333333rem';
76
+ } else {
77
+ wrapper = document.createElement('div');
78
+
79
+ wrapper.className = 'u-position-relative';
80
+ this.el.parentNode.insertBefore(wrapper, this.el);
81
+ wrapper.appendChild(this.el);
82
+
83
+ this.el.style.paddingRight = '3.8333333333rem';
84
+ }
85
+
86
+ counter.className = `color-granit ${className('a-meta')}`;
87
+ counter.style.cssText = 'position: absolute; top: 5px; right: 10px; z-index: 501;';
88
+
89
+ wrapper.appendChild(counter);
90
+
91
+ this.counterEl = counter;
92
+ this.setCountText();
93
+ }
94
+
95
+ attach() {
96
+ if (this.isRichText) {
97
+ this.el.editor.on('update', () => {
98
+ this.setCountText();
99
+ });
100
+ } else {
101
+ this.el.addEventListener('input', () => {
102
+ this.setCountText();
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ const elements = document.querySelectorAll('[data-min], [data-max]');
109
+ elements.forEach((el) => {
110
+ el.charCounter = new CharCounter(el);
111
+ });
@@ -0,0 +1,7 @@
1
+ export default function htmlTextLength(html) {
2
+ const div = document.createElement('div');
3
+
4
+ div.innerHTML = html;
5
+
6
+ return div.textContent.length;
7
+ }
@@ -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]');
15
+ const toggleTextButtons = document.querySelectorAll('[data-toggle-text]');
16
16
 
17
- if (toggleTextButton) {
18
- toggleTextButton.addEventListener('click', toggleTextOnClick);
17
+ if (toggleTextButtons) {
18
+ [].forEach.call(toggleTextButtons, (toggleTextButton) => {
19
+ toggleTextButton.addEventListener('click', toggleTextOnClick);
20
+ });
19
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) {
@@ -186,6 +186,14 @@ export function setupTextArea(el, onChange = () => {}) {
186
186
  el.parentNode.insertBefore(editorEl, el);
187
187
 
188
188
  createToolbar(editorEl, editor);
189
+
190
+ const event = new CustomEvent('editor-ready', {
191
+ detail: {
192
+ editor,
193
+ },
194
+ });
195
+
196
+ el.dispatchEvent(event);
189
197
  }
190
198
 
191
199
  export function init() {
@@ -51,6 +51,44 @@ module.exports = {
51
51
  is_richtext: true,
52
52
  has_help: false
53
53
  }
54
+ },
55
+ {
56
+ name: 'Counter (max)',
57
+ status: 'wip',
58
+ context: {
59
+ is_richtext: false,
60
+ has_help: false,
61
+ max: 300,
62
+ },
63
+ },
64
+ {
65
+ name: 'Counter (min)',
66
+ status: 'wip',
67
+ context: {
68
+ is_richtext: false,
69
+ has_help: false,
70
+ min: 30,
71
+ },
72
+ },
73
+ {
74
+ name: 'Counter (min and max)',
75
+ status: 'wip',
76
+ context: {
77
+ is_richtext: false,
78
+ has_help: false,
79
+ min: 30,
80
+ max: 300,
81
+ },
82
+ },
83
+ {
84
+ name: 'Rich text with counter',
85
+ status: 'wip',
86
+ context: {
87
+ is_richtext: true,
88
+ has_help: true,
89
+ min: 30,
90
+ max: 300,
91
+ },
54
92
  }
55
93
  ]
56
94
  }
package/src/components.js CHANGED
@@ -30,4 +30,8 @@ import './organisms/timeline/timeline';
30
30
  import './molecules/overview-navigation/overview-navigation';
31
31
  import './organisms/schedule/schedule-filter';
32
32
  import './assets/js/ot';
33
+ import './assets/js/charCounter';
33
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,116 @@
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: 10px;
30
+ cursor: pointer;
31
+ background-color: #fff;
32
+ border: none;
33
+ border-bottom: 1px solid #d4d4d4;
34
+ width: 100%;
35
+ text-align: left;
36
+
37
+ &:hover,
38
+ &.autocomplete-active {
39
+ background-color: $color-ocean;
40
+ color: $color-snow;
41
+ }
42
+ }
43
+ }
44
+
45
+ /* Selected items container */
46
+ .selected-items-container {
47
+ margin-top: -#{rhythm(1)};
48
+ padding: rhythm(2) rhythm(1) rhythm(1) rhythm(1);
49
+ border: 1px solid #d4d4d4;
50
+ background-color: $color-concrete;
51
+ border-bottom-left: $border-radius;
52
+ border-bottom-right-radius: $border-radius;
53
+ }
54
+
55
+ .selected-items-container:empty {
56
+ display: none;
57
+ }
58
+
59
+ /* Style for selected items */
60
+ .selected-items-container li {
61
+ margin-right: 5px;
62
+ padding: 5px;
63
+ border: 1px solid #ccc;
64
+ background-color: #e4e4e4;
65
+ border-radius: 4px;
66
+ display: inline-flex;
67
+ align-items: center;
68
+ margin-bottom: 0;
69
+ font-size: 90%;
70
+ }
71
+
72
+ /* Remove button for selected items */
73
+ .selected-items-container button {
74
+ margin-left: 5px;
75
+ border: none;
76
+ background-color: #d80000;
77
+ color: white;
78
+ border-radius: 50%;
79
+ cursor: pointer;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ line-height: 1;
84
+ width: 16px;
85
+ height: 16px;
86
+ position: relative;
87
+ }
88
+
89
+ .selected-items-container button::after {
90
+ content: '';
91
+ display: block;
92
+ width: 16px;
93
+ height: 16px;
94
+ position: absolute;
95
+ top: 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: 8px 8px;
101
+ }
102
+
103
+ .selected-items-container button:hover {
104
+ background-color: darkred;
105
+ }
106
+
107
+ .visually-hidden {
108
+ position: absolute;
109
+ width: 1px;
110
+ height: 1px;
111
+ margin: -1px;
112
+ padding: 0;
113
+ overflow: hidden;
114
+ clip: rect(0 0 0 0);
115
+ clip: rect(0, 0, 0, 0);
116
+ }
@@ -0,0 +1,159 @@
1
+ /* eslint-disable */
2
+ // Get references to the search input and suggestions box elements
3
+ const className = 'm-multi-select';
4
+ const multiSelectElement = document.querySelector(`.js-${className}`);
5
+ const multiSelectInput = document.querySelector(`.js-${className}__input`);
6
+ const suggestionsBox = document.querySelector(`.js-${className}-suggestions-box`);
7
+
8
+ if (multiSelectInput && suggestionsBox) {
9
+ let currentFocus = -1; // Tracks the currently focused item in the suggestions
10
+ const namespace = getComputedStyle(multiSelectElement, ':before').content.replace(/["']/g, '');
11
+
12
+ // Function to 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
+ // Function to remove highlighting from all suggestions
24
+ function removeActive(items) {
25
+ for (let i = 0; i < items.length; i++) {
26
+ items[i].classList.remove('autocomplete-active');
27
+ }
28
+ }
29
+
30
+ // Function to add a selected item to the list of selected items
31
+ function addSelectedItem(item) {
32
+ const container = document.getElementById('selectedItemsContainer');
33
+ const newItem = document.createElement('li');
34
+ newItem.textContent = item + ' ';
35
+ const removeBtn = document.createElement('button');
36
+ const buttonTextContainer = document.createElement('span');
37
+ //removeBtn.textContent = 'x';
38
+ buttonTextContainer.classList.add('visually-hidden');
39
+ removeBtn.appendChild(buttonTextContainer);
40
+ buttonTextContainer.textContent ='Remove ' + item; // Accessibility label for screen readers
41
+ // Event listener for removing the selected item
42
+ removeBtn.addEventListener('click', function () {
43
+ removeItem(newItem, Array.from(container.children).indexOf(newItem));
44
+ });
45
+ newItem.appendChild(removeBtn);
46
+ container.appendChild(newItem);
47
+ }
48
+
49
+ // Function to remove an item and manage focus appropriately
50
+ function removeItem(item, index) {
51
+ const container = document.getElementById('selectedItemsContainer');
52
+ container.removeChild(item);
53
+
54
+ let remainingItems = container.getElementsByTagName('div');
55
+ // Focus management: set focus to the next item, or the search input if no items left
56
+ if (remainingItems.length > 0) {
57
+ if (index < remainingItems.length) {
58
+ remainingItems[index].getElementsByTagName('button')[0].focus();
59
+ } else {
60
+ remainingItems[remainingItems.length - 1].getElementsByTagName('button')[0].focus();
61
+ }
62
+ } else {
63
+ multiSelectInput.focus();
64
+ }
65
+ }
66
+
67
+ // Event listener for input changes in the search field
68
+ multiSelectInput.addEventListener('input', function () {
69
+ const value = this.value;
70
+ // Clear suggestions if less than 2 characters are typed
71
+ if (value.length < 2) {
72
+ suggestionsBox.innerHTML = '';
73
+ return;
74
+ }
75
+
76
+ // Define a JSON array of 100 real cities
77
+ let suggestions = [
78
+ { name: 'New York' }, { name: 'Los Angeles' }, { name: 'Chicago' },
79
+ { name: 'Houston' }, { name: 'Phoenix' }, { name: 'Philadelphia' },
80
+ { name: 'San Antonio' }, { name: 'San Diego' }, { name: 'Dallas' },
81
+ { name: 'San Jose' }, { name: 'Austin' }, { name: 'Jacksonville' },
82
+ { name: 'Fort Worth' }, { name: 'Columbus' }, { name: 'San Francisco' },
83
+ { name: 'Tokyo' }, { name: 'Delhi' }, { name: 'Shanghai' },
84
+ { name: 'Sao Paulo' }, { name: 'Mumbai' }, { name: 'Beijing' },
85
+ { name: 'Cairo' }, { name: 'Dhaka' }, { name: 'Mexico City' },
86
+ { name: 'Osaka' }, { name: 'Karachi' }, { name: 'Chongqing' },
87
+ { name: 'Istanbul' }, { name: 'Buenos Aires' }, { name: 'Kolkata' },
88
+ { name: 'Kinshasa' }, { name: 'Lagos' }, { name: 'Manila' },
89
+ { name: 'Tianjin' }, { name: 'Rio de Janeiro' }, { name: 'Guangzhou' },
90
+ { name: 'Lahore' }, { name: 'Moscow' }, { name: 'Shenzhen' },
91
+ { name: 'Bangalore' }, { name: 'Paris' }, { name: 'Bogota' },
92
+ { name: 'Jakarta' }, { name: 'Chennai' }, { name: 'Lima' },
93
+ { name: 'Bangkok' }, { name: 'Seoul' }, { name: 'Nagoya' },
94
+ { name: 'Hyderabad' }, { name: 'London' }, { name: 'Tehran' },
95
+ { name: 'Chengdu' }, { name: 'Nanjing' },
96
+ { name: 'Wuhan' }, { name: 'Ho Chi Minh City' }, { name: 'Luanda' },
97
+ { name: 'Ahmedabad' }, { name: 'Kuala Lumpur' }, { name: 'Xi’an' },
98
+ { name: 'Hong Kong' }, { name: 'Dongguan' }, { name: 'Hangzhou' },
99
+ { name: 'Foshan' }, { name: 'Shenyang' }, { name: 'Riyadh' },
100
+ { name: 'Baghdad' }, { name: 'Santiago' }, { name: 'Surat' },
101
+ { name: 'Madrid' }, { name: 'Suzhou' }, { name: 'Pune' },
102
+ { name: 'Harbin' }, { name: 'Houston' },
103
+ { name: 'Toronto' }, { name: 'Dar es Salaam' }, { name: 'Miami' },
104
+ { name: 'Belo Horizonte' }, { name: 'Singapore' },
105
+ { name: 'Atlanta' }, { name: 'Fukuoka' }, { name: 'Khartoum' },
106
+ { name: 'Barcelona' }, { name: 'Johannesburg' }, { name: 'Saint Petersburg' },
107
+ { name: 'Qingdao' }, { name: 'Dalian' }, { name: 'Washington, D.C.' },
108
+ { name: 'Yangon' }, { name: 'Alexandria' }, { name: 'Jinan' },
109
+ { name: 'Guadalajara' }, { name: 'Sydney' }, { name: 'Melbourne' },
110
+ { name: 'Montreal' }, { name: 'Ankara' }, { name: 'Recife' },
111
+ { name: 'Durban' }, { name: 'Porto Alegre' },
112
+ { name: 'Dusseldorf' }, { name: 'Hamburg' }, { name: 'Cape Town' },
113
+ ];
114
+
115
+ // Filter suggestions based on the input value
116
+ const filtered = suggestions.filter(item => item.name.toLowerCase().startsWith(value.toLowerCase()));
117
+ // Populate the suggestions box with the filtered results
118
+ suggestionsBox.innerHTML = filtered.map(item => `<button class='${namespace}m-multi-select__suggestion-btn' tabindex='0'>${item.name}</button>`).join('');
119
+ // Reset the current focus
120
+ currentFocus = -1;
121
+ });
122
+
123
+ // Event listener for keydown events for navigation and selection in the suggestions box
124
+ multiSelectInput.addEventListener('keydown', function (e) {
125
+ let items = suggestionsBox.getElementsByClassName(`${namespace}m-multi-select__suggestion-btn`);
126
+ // Navigate down in the suggestions list
127
+ if (e.keyCode == 40) {
128
+ currentFocus = (currentFocus + 1) % items.length;
129
+ setActive(items);
130
+ // Navigate up in the suggestions list
131
+ } else if (e.keyCode == 38) {
132
+ currentFocus = (currentFocus - 1 + items.length) % items.length;
133
+ setActive(items);
134
+ // Handle Enter key to select a focused item
135
+ } else if (e.keyCode == 13) {
136
+ e.preventDefault();
137
+ if (currentFocus > -1 && items[currentFocus]) {
138
+ addSelectedItem(items[currentFocus].textContent);
139
+ suggestionsBox.innerHTML = '';
140
+ multiSelectInput.value = '';
141
+ currentFocus = -1;
142
+ }
143
+ }
144
+ });
145
+
146
+ // Click event listener for the suggestions box
147
+ suggestionsBox.addEventListener('click', function (e) {
148
+ // Add the clicked suggestion to the selected items list
149
+ if (e.target && e.target.classList.contains('suggestion-btn')) {
150
+ addSelectedItem(e.target.textContent);
151
+ suggestionsBox.innerHTML = '';
152
+ multiSelectInput.value = '';
153
+ }
154
+ });
155
+
156
+
157
+
158
+
159
+ }
@@ -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
+ }