@eric-skaftason/web-components 1.0.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.
Files changed (46) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +2 -0
  3. package/index.js +0 -0
  4. package/package.json +19 -0
  5. package/src/card/card.html +7 -0
  6. package/src/card/card.js +102 -0
  7. package/src/card/card_container.html +16 -0
  8. package/src/card/styles/default.css +12 -0
  9. package/src/card/styles/flat.css +7 -0
  10. package/src/card/styles/universal.css +65 -0
  11. package/src/dropdown/dropdown.js +120 -0
  12. package/src/dropdown/dropdown_element.html +27 -0
  13. package/src/dropdown/dropdown_menu.html +58 -0
  14. package/src/modal/list_add_element/list_add_element.html +44 -0
  15. package/src/modal/list_add_element/list_add_element.js +53 -0
  16. package/src/modal/list_element/list_element.html +42 -0
  17. package/src/modal/list_element/list_element.js +28 -0
  18. package/src/modal/list_input/list_input.html +28 -0
  19. package/src/modal/list_input/list_input.js +174 -0
  20. package/src/modal/menu_body/menu_body.html +38 -0
  21. package/src/modal/menu_body/menu_body.js +22 -0
  22. package/src/modal/menu_button/menu_button.html +32 -0
  23. package/src/modal/menu_button/menu_button.js +15 -0
  24. package/src/modal/menu_controller/menu_controller.html +22 -0
  25. package/src/modal/menu_controller/menu_controller.js +38 -0
  26. package/src/modal/menu_controls/menu_controls.html +19 -0
  27. package/src/modal/menu_controls/menu_controls.js +15 -0
  28. package/src/modal/menu_header/menu_header.html +16 -0
  29. package/src/modal/menu_header/menu_header.js +15 -0
  30. package/src/modal/menu_text/menu_text.html +18 -0
  31. package/src/modal/menu_text/menu_text.js +15 -0
  32. package/src/modal/menu_title/menu_title.html +21 -0
  33. package/src/modal/menu_title/menu_title.js +15 -0
  34. package/src/modal/modal_menu/modal_menu.html +39 -0
  35. package/src/modal/modal_menu/modal_menu.js +42 -0
  36. package/src/modal/modal_menu.js +15 -0
  37. package/src/modal-lite/modal_menu.css +122 -0
  38. package/src/nav_bar/nav_bar.html +178 -0
  39. package/src/nav_bar/nav_bar.js +50 -0
  40. package/src/progress_bar/progress_bar.html +21 -0
  41. package/src/progress_bar/progress_bar.js +58 -0
  42. package/tests/card.html +17 -0
  43. package/tests/dropdown.html +0 -0
  44. package/tests/modal.html +0 -0
  45. package/tests/nav_bar.html +0 -0
  46. package/tests/progress_bar.html +0 -0
@@ -0,0 +1,120 @@
1
+ class DropdownMenu extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+
5
+ this.isOpen = false; // menu open/closed
6
+ this.subOpen = false; // sub menu open/closed
7
+ this.attachShadow({ mode: 'open' });
8
+ }
9
+ subMenuOpen() {
10
+ this.shadowRoot.querySelector('')
11
+ }
12
+
13
+ getMenuPos() {
14
+ const controller = this.shadowRoot.querySelector('.dropdown_menu_controller');
15
+ const openFrom = this.getAttribute('openFrom');
16
+
17
+ switch (openFrom) {
18
+ case 'right':
19
+ return {
20
+ x: controller.getBoundingClientRect().right,
21
+ y: controller.getBoundingClientRect().top
22
+ };
23
+
24
+ // Default / bottom
25
+ default:
26
+ return {
27
+ x: controller.getBoundingClientRect().left,
28
+ y: controller.getBoundingClientRect().bottom
29
+ };
30
+ }
31
+ }
32
+
33
+ open() {
34
+ const controller = this.shadowRoot.querySelector('.dropdown_menu_controller');
35
+ const dropdown_menu = this.shadowRoot.querySelector('.dropdown_menu');
36
+ dropdown_menu.style.display = 'flex';
37
+
38
+ const {x: menuX, y: menuY} = this.getMenuPos();
39
+
40
+ dropdown_menu.style.top = `${menuY}px`;
41
+ dropdown_menu.style.left = `${menuX}px`;
42
+
43
+ dropdown_menu.style.maxWidth = `${controller.getBoundingClientRect().width}px`;
44
+
45
+
46
+ // Change controller text
47
+ controller.innerText = `▼ ${this.desc}`;
48
+
49
+ this.isOpen = true;
50
+ }
51
+
52
+ close() {
53
+ const controller = this.shadowRoot.querySelector('.dropdown_menu_controller');
54
+ const dropdown_menu = this.shadowRoot.querySelector('.dropdown_menu');
55
+ dropdown_menu.style.display = 'none';
56
+ controller.innerText = `▶ ${this.desc}`;
57
+
58
+ this.isOpen = false;
59
+ }
60
+
61
+ async connectedCallback() {
62
+ // Set shadow DOM HTML
63
+ const res = await fetch('/components/dropdown/dropdown_menu.html');
64
+ const html = await res.text();
65
+
66
+ this.shadowRoot.innerHTML = html;
67
+
68
+ // set text
69
+ const controller = this.shadowRoot.querySelector('.dropdown_menu_controller');
70
+
71
+ this.desc = this.getAttribute('desc') || 'dropdown menu';
72
+ controller.innerText = `▶ ${this.desc}`;
73
+
74
+ // Add event listeners
75
+ controller.addEventListener('click', (event) => {
76
+ this.isOpen ? this.close() : this.open();
77
+ });
78
+
79
+ document.addEventListener('click', (event) => {
80
+ // composedPath returns array ordered from deepest element
81
+ // Thus, it works with shadow DOM
82
+ // event.target doesn't work because shadow DOM hides the nodes
83
+ const path = event.composedPath();
84
+
85
+ // some method checks if any element matches the condition
86
+ const clickedDropdownElement = path.some(node =>
87
+ node.tagName === 'DROPDOWN-ELEMENT'
88
+ ); // tag name property is always in upercase
89
+
90
+ // Run close method if the path includes 'this' HTML element or if a dropdown element was clicked
91
+ if (!path.includes(this) || clickedDropdownElement) this.close();
92
+ });
93
+ }
94
+ }
95
+
96
+ customElements.define('dropdown-menu', DropdownMenu);
97
+
98
+
99
+ class DropdownElement extends HTMLElement {
100
+ constructor() {
101
+ super();
102
+ this.attachShadow({ mode: 'open' });
103
+ }
104
+
105
+ async connectedCallback() {
106
+ const res = await fetch('/components/dropdown/dropdown_element.html');
107
+ const html = await res.text();
108
+
109
+ this.shadowRoot.innerHTML = html;
110
+
111
+ this.addEventListener('click', () => {
112
+ const link = this.getAttribute('link') || null;
113
+ if (!link) return;
114
+
115
+ document.location = link;
116
+ });
117
+ }
118
+ }
119
+
120
+ customElements.define('dropdown-element', DropdownElement);
@@ -0,0 +1,27 @@
1
+ <style>
2
+ .dropdown_element {
3
+ display: flex;
4
+ justify-content: center;
5
+ align-items: center;
6
+ width: 100%;
7
+ height: 100%;
8
+ box-sizing: border-box;
9
+
10
+ cursor: pointer;
11
+ }
12
+
13
+ .dropdown_element:hover {
14
+ background-color: rgba(255, 255, 255, 0.95);
15
+ }
16
+
17
+ ::slotted(*) {
18
+ font-family: 'Courier New', Courier, monospace;
19
+ font-size: 16px;
20
+ }
21
+
22
+ </style>
23
+ <div class="dropdown_element">
24
+ <slot>
25
+
26
+ </slot>
27
+ </div>
@@ -0,0 +1,58 @@
1
+ <style>
2
+ .dropdown_menu_controller {
3
+ display: flex;
4
+ box-sizing: border-box;
5
+ width: 100%;
6
+ height: 50px;
7
+
8
+ font-family: 'Courier New', Courier, monospace;
9
+ font-size: 16px;
10
+ justify-content: center;
11
+ align-items: center;
12
+
13
+ user-select: none;
14
+
15
+ cursor: pointer;
16
+ }
17
+
18
+ .dropdown_menu_controller:hover {
19
+ text-decoration: underline;
20
+ background-color: rgba(255, 255, 255, 0.95);
21
+ border: 1px solid #00469c;
22
+ }
23
+
24
+
25
+ /* Styling for the menu itself */
26
+ .dropdown_menu {
27
+ /* Initially not visible */
28
+ display: none;
29
+
30
+ flex-direction: column;
31
+ position: fixed;
32
+
33
+ top: 0;
34
+ left: 0;
35
+
36
+ box-sizing: border-box;
37
+ width: 250px;
38
+ }
39
+
40
+ /* styling for elements within the menu */
41
+ ::slotted(*) {
42
+ box-sizing: border-box;
43
+ user-select: none;
44
+
45
+ flex: 0 0 50px;
46
+ width: 100%;
47
+
48
+ background-color: rgba(255, 255, 255, 0.9);
49
+ }
50
+
51
+ </style>
52
+ <div class="dropdown_menu_controller"></div>
53
+ <div class="dropdown_menu">
54
+ <!-- use slots so HTML elements can be passed into the custom dropdown -->
55
+ <slot>
56
+
57
+ </slot>
58
+ </div>
@@ -0,0 +1,44 @@
1
+ <script src="/components/modal/menu_button/menu_button.js"></script>
2
+
3
+ <style>
4
+ :host {
5
+ box-sizing: border-box;
6
+ width: 100%;
7
+ color: #f5f5f5;
8
+
9
+ padding-left: 10px;
10
+ margin-bottom: 10px;
11
+
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+
16
+ background-color: #3a3a3a;
17
+
18
+ border: 1px solid #1a1a1a;
19
+
20
+ cursor: text;
21
+ transition: background-color 0.2s ease;
22
+ }
23
+
24
+ :host(:hover) {
25
+ background-color: #292929;
26
+ }
27
+
28
+ .text_input {
29
+ all: unset;
30
+
31
+ width: 100%;
32
+ height: 100%;
33
+
34
+ }
35
+
36
+ </style>
37
+
38
+ <slot hidden></slot>
39
+ <input type="text" class="text_input" id="text_input_add">
40
+
41
+ </input>
42
+ <div class="element_functions">
43
+ <menu-button id="add">+</menu-button>
44
+ </div>
@@ -0,0 +1,53 @@
1
+ // List element
2
+ export class ListAddElement extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.attachShadow({ mode: 'open' });
6
+ }
7
+
8
+ getTextFromSlot() {
9
+ const slot = this.shadowRoot.querySelector('slot');
10
+ const text = slot.assignedNodes({flatten: true}).filter(n => n.nodeType === Node.TEXT_NODE).map(n => n.textContent.trim()).join('');
11
+
12
+ return text;
13
+ }
14
+
15
+ async connectedCallback() {
16
+ const res = await fetch('/components/modal/list_add_element/list_add_element.html');
17
+ const html = await res.text();
18
+
19
+ this.shadowRoot.innerHTML = html;
20
+
21
+ // Get text from slot
22
+ const text = this.getTextFromSlot();
23
+
24
+ const text_input = this.shadowRoot.querySelector('.text_input');
25
+ text_input.placeholder = text;
26
+
27
+ // running obj.method in an event listener doesn't work becasue it calls method instead of obj.method
28
+ // method must be bound to the object manually
29
+ this.shadowRoot.querySelector('#add').addEventListener('click', this.addElement.bind(this));
30
+ this.shadowRoot.querySelector('#text_input_add').addEventListener('keydown', (event) => {
31
+ if (event.key === 'Enter') this.addElement();
32
+ });
33
+ }
34
+
35
+ async addElement() {
36
+ // Add element
37
+ const newElementText = this.shadowRoot.querySelector('.text_input').value;
38
+ const newElement = await this.shadowRoot.host.closest('list-input').appendElement(newElementText);
39
+
40
+ // if new element was created
41
+ if (newElement) {
42
+ // Scroll to bottom of body
43
+ setTimeout(() => {
44
+ // this.shadowRoot.host.closest('menu-body').scrollToEnd();
45
+ newElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
46
+ }, 50);
47
+
48
+ // clear input box
49
+ this.shadowRoot.querySelector('#text_input_add').value = '';
50
+ }
51
+ }
52
+ }
53
+ customElements.define('list-add-element', ListAddElement);
@@ -0,0 +1,42 @@
1
+ <script src="/components/modal/menu_button/menu_button.js"></script>
2
+
3
+ <style>
4
+ :host {
5
+ box-sizing: border-box;
6
+ width: 100%;
7
+ color: #f5f5f5;
8
+
9
+ padding-left: 10px;
10
+ margin-bottom: 10px;
11
+
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+
16
+ background-color: #3a3a3a;
17
+
18
+ cursor: text;
19
+ transition: background-color 0.2s ease;
20
+ }
21
+
22
+ :host(:hover) {
23
+ background-color: #292929;
24
+ }
25
+
26
+ .text_input {
27
+ all: unset;
28
+
29
+ width: 100%;
30
+ height: 100%;
31
+
32
+ }
33
+
34
+ </style>
35
+
36
+ <slot hidden></slot>
37
+ <input type="text" class="text_input">
38
+
39
+ </input>
40
+ <div class="element_functions">
41
+ <menu-button id="del">🗑</menu-button>
42
+ </div>
@@ -0,0 +1,28 @@
1
+ // List element
2
+ export class ListElement extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.attachShadow({ mode: 'open' });
6
+ }
7
+
8
+ async connectedCallback() {
9
+ const res = await fetch('/components/modal/list_element/list_element.html');
10
+ const html = await res.text();
11
+
12
+ this.shadowRoot.innerHTML = html;
13
+
14
+ // Get text
15
+ const slot = this.shadowRoot.querySelector('slot');
16
+ const text = slot.assignedNodes({flatten: true}).filter(n => n.nodeType === Node.TEXT_NODE).map(n => n.textContent.trim()).join('');
17
+
18
+ const text_input = this.shadowRoot.querySelector('.text_input');
19
+ text_input.value = text;
20
+
21
+
22
+ this.shadowRoot.querySelector('#del').addEventListener('click', async (event) => {
23
+ const listElement = this.shadowRoot.host;
24
+ await this.shadowRoot.host.closest('list-input').removeElement(listElement);
25
+ });
26
+ }
27
+ }
28
+ customElements.define('list-element', ListElement);
@@ -0,0 +1,28 @@
1
+ <script src="/components/modal/menu_button/menu_button.js"></script>
2
+ <script src="/components/modal/modal_menu/modal_menu.js"></script>
3
+
4
+ <style>
5
+ :host {
6
+ color: #f5f5f5;
7
+
8
+ padding: 10px;
9
+
10
+ display: flex;
11
+ flex-direction: column;
12
+ justify-content: center;
13
+ align-items: center;
14
+
15
+ background-color: #464646;
16
+ }
17
+
18
+ .function_row {
19
+ width: 100%;
20
+ display: flex;
21
+ flex-direction: row;
22
+ }
23
+ </style>
24
+
25
+ <slot hidden></slot>
26
+ <!-- <div class="function_row">
27
+ <menu-button>Expand/shrink</menu-button>
28
+ </div> -->
@@ -0,0 +1,174 @@
1
+ // List input
2
+ export class ListInput extends HTMLElement {
3
+ #elements = []; // should be an array of strings
4
+ #maxStringLen = 64;
5
+
6
+ #isValidStringExternal = () => {
7
+ return true;
8
+ }
9
+
10
+ #isValidStringExternalAsync = async () => {
11
+ return true;
12
+ }
13
+
14
+ constructor() {
15
+ super();
16
+ this.attachShadow({ mode: 'open' });
17
+ }
18
+
19
+ modalMessage(message) {
20
+ const modal_menu = this.shadowRoot.host.closest('modal-menu');
21
+ modal_menu.insertAdjacentHTML('afterend',
22
+ `
23
+ <modal-menu width="200px" height="200px" opacity="0" center>
24
+ <menu-controls>
25
+ <close-menu></close-menu>
26
+ </menu-controls>
27
+ <menu-body>
28
+ <menu-text>${message}</menu-text>
29
+ <close-menu>OK</close-menu>
30
+ </menu-body>
31
+ </modal-menu>
32
+ `
33
+ );
34
+ }
35
+
36
+ async connectedCallback() {
37
+ const res = await fetch('/components/modal/list_input/list_input.html');
38
+ const html = await res.text();
39
+
40
+ this.shadowRoot.innerHTML = html;
41
+
42
+ // initialise #elements
43
+ const list_elements = this.shadowRoot.host.querySelectorAll('list-element');
44
+
45
+ for (let i = list_elements.length - 1; i >= 0; i--) {
46
+ const list_element = list_elements[i];
47
+ let element_text = list_element.innerText;
48
+
49
+ // ENFORCES MAX STRING LENGTH
50
+ // DOES NOT ENFORCE STRING VALIDITY FOR SLOTTED STRINGS
51
+ if (element_text.length > this.#maxStringLen) {
52
+ element_text = element_text.slice(0, this.#maxStringLen);
53
+ list_elements[i].innerText = element_text;
54
+ }
55
+
56
+ this.#elements.unshift(element_text);
57
+ }
58
+
59
+ // slot is visible to this.shadowRoot but not this.shadowRoot.host
60
+ // slotted elements are projected to the shadow dom, and slot is removed
61
+ this.shadowRoot.querySelector('slot').hidden = false;
62
+
63
+
64
+ // Connect isValidString to src
65
+ const verificationScriptSrc = this.shadowRoot.host.getAttribute('verify-str-src');
66
+ if (verificationScriptSrc) {
67
+ try {
68
+ const module = await import(verificationScriptSrc);
69
+ if (typeof module.isValidString !== 'function') throw new Error('Missing isValidString function.');
70
+
71
+ if (module.isValidString.constructor.name === "AsyncFunction") {
72
+ this.#isValidStringExternalAsync = module.isValidString;
73
+ } else {
74
+ this.#isValidStringExternal = module.isValidString;
75
+ }
76
+
77
+ } catch (err) {
78
+ console.error('Module failed to load:', err);
79
+ }
80
+ }
81
+ }
82
+
83
+ async isValidString(string) {
84
+ if (typeof string !== 'string') return false;
85
+
86
+ if (string.length > this.#maxStringLen) return false;
87
+
88
+ const additional_args = this.shadowRoot.host.getAttribute('verify-str-args');
89
+ const additional_args_parsed = additional_args !== null ? JSON.parse(additional_args): [];
90
+
91
+ const validSync = this.#isValidStringExternal(string, ...additional_args_parsed);
92
+ const validAsync = await this.#isValidStringExternalAsync(string, ...additional_args_parsed);
93
+ if (validSync !== true || validAsync !== true) {
94
+
95
+ const errorMsg = (() => {
96
+ if (typeof validSync === 'string') return validSync;
97
+ if (typeof validAsync === 'string') return validAsync;
98
+ return null;
99
+ })();
100
+ if (errorMsg) {
101
+ this.modalMessage(errorMsg);
102
+ }
103
+
104
+ return false;
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ getNewElement(text) {
111
+ const list_element = document.createElement('list-element');
112
+ list_element.innerText = text;
113
+
114
+ return list_element;
115
+ }
116
+
117
+ getElements() {
118
+ return this.#elements;
119
+ }
120
+
121
+ getElementIndex(element) {
122
+ const allElements = Array.from(this.shadowRoot.host.querySelectorAll('list-element'));
123
+ return allElements.indexOf(element);
124
+ }
125
+
126
+ async prependElement(text) {
127
+ if (!await this.isValidString(text)) return;
128
+
129
+ const newElement = this.getNewElement(text);
130
+
131
+ this.#elements.unshift(text);
132
+ this.shadowRoot.host.prepend(newElement);
133
+
134
+ return newElement;
135
+ }
136
+
137
+ async addElement(index, text) {
138
+ if (!await this.isValidString(text)) return;
139
+ if (index < 0 || index >= this.#elements.length) return;
140
+
141
+ if (index === this.#elements.length - 1) return this.appendElement(text);
142
+ if (index === 0) return this.prependElement(text);
143
+
144
+ // insert at index, remove 0, insert element
145
+ this.#elements.splice(index, 0, text);
146
+
147
+ const newElement = this.getNewElement(text);
148
+
149
+ this.shadowRoot.host.insertBefore(newElement, this.shadowRoot.host.querySelectorAll('list-element')[index]);
150
+
151
+ return newElement;
152
+ }
153
+
154
+ async appendElement(text) {
155
+ if (!await this.isValidString(text)) return;
156
+
157
+ this.#elements.push(text);
158
+
159
+ const newElement = this.getNewElement(text);
160
+
161
+ this.shadowRoot.host.insertBefore(newElement, this.shadowRoot.host.querySelector('list-add-element'));
162
+
163
+ return newElement;
164
+ }
165
+
166
+ async removeElement(element) {
167
+ const index = this.getElementIndex(element);
168
+ if (index == null || index < 0 || index >= this.#elements.length) return;
169
+
170
+ this.#elements.splice(index, 1);
171
+ element.remove();
172
+ }
173
+ }
174
+ customElements.define('list-input', ListInput);
@@ -0,0 +1,38 @@
1
+ <style>
2
+ :host {
3
+ box-sizing: border-box;
4
+ width: 100%;
5
+
6
+ padding: 10px;
7
+
8
+ display: flex;
9
+ flex-direction: column;
10
+
11
+ border-top: 1px solid #dfdfdf;
12
+ /* border-bottom: 1px solid #dfdfdf; */
13
+
14
+ user-select: none;
15
+
16
+ overflow-y: auto;
17
+ }
18
+
19
+ :host::-webkit-scrollbar {
20
+ width: 8px;
21
+ }
22
+
23
+ :host::-webkit-scrollbar-thumb {
24
+ background-color: #1880ffbd;
25
+ border-radius: 4px;
26
+ }
27
+
28
+ :host::-webkit-scrollbar-track {
29
+ background-color: #00295a;
30
+ }
31
+
32
+ host::-webkit-scrollbar-button {
33
+ display: none;
34
+ }
35
+
36
+ </style>
37
+
38
+ <slot></slot>
@@ -0,0 +1,22 @@
1
+ // Menu body (container)
2
+ export class MenuBody extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.attachShadow({ mode: 'open' });
6
+ }
7
+
8
+ async connectedCallback() {
9
+ const res = await fetch('/components/modal/menu_body/menu_body.html');
10
+ const html = await res.text();
11
+
12
+ this.shadowRoot.innerHTML = html;
13
+ }
14
+
15
+ scrollToEnd() {
16
+ this.shadowRoot.host.scrollTo({
17
+ top: this.shadowRoot.host.scrollHeight,
18
+ behavior: 'smooth'
19
+ });
20
+ }
21
+ }
22
+ customElements.define('menu-body', MenuBody);
@@ -0,0 +1,32 @@
1
+ <style>
2
+ :host {
3
+ box-sizing: border-box;
4
+ width: 100%;
5
+ min-height: 50px;
6
+ height: 50px;
7
+ min-width: 50px;
8
+ padding: 0 5px 0 5px;
9
+
10
+ color: #f5f5f5;
11
+
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+
16
+ background-color: #292929;
17
+
18
+ cursor: pointer;
19
+ transition: background-color 0.2s ease, transform 0.1s ease;
20
+ }
21
+
22
+ :host(:hover) {
23
+ background-color: #1a1a1a;
24
+ }
25
+
26
+ :host(:active) {
27
+ transform: scale(0.98);
28
+ }
29
+ </style>
30
+
31
+
32
+ <slot></slot>
@@ -0,0 +1,15 @@
1
+ // Menu button
2
+ export class MenuButton extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.attachShadow({ mode: 'open' });
6
+ }
7
+
8
+ async connectedCallback() {
9
+ const res = await fetch('/components/modal/menu_button/menu_button.html');
10
+ const html = await res.text();
11
+
12
+ this.shadowRoot.innerHTML = html;
13
+ }
14
+ }
15
+ customElements.define('menu-button', MenuButton);