kemet-ui 1.1.0 → 1.2.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.
- package/custom-elements.json +910 -331
- package/dist/components/kemet-fab/kemet-fab.js +209 -0
- package/dist/components/kemet-input/kemet-input.js +15 -4
- package/dist/components/kemet-password/kemet-password.js +278 -0
- package/dist/components/kemet-tracker/kemet-tracker-step.js +239 -0
- package/dist/components/kemet-tracker/kemet-tracker.js +76 -0
- package/dist/kemet-components.cjs +862 -51
- package/dist/kemet-components.js +862 -51
- package/dist/kemet-components.min.js +409 -93
- package/dist/utilities/controllers/forms.js +4 -2
- package/package.json +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { html, css, LitElement } from 'lit';
|
|
2
|
+
|
|
3
|
+
export default class KemetFAB extends LitElement {
|
|
4
|
+
static get styles() {
|
|
5
|
+
return [
|
|
6
|
+
css`
|
|
7
|
+
:host {
|
|
8
|
+
--kemet-fab-size: 50px;
|
|
9
|
+
|
|
10
|
+
display: inline-block;
|
|
11
|
+
position: relative;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
button {
|
|
15
|
+
color: var(--kemet-fab-color, var(--kemet-color-white));
|
|
16
|
+
font-size: inherit;
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
padding: 0;
|
|
19
|
+
position: relative;
|
|
20
|
+
min-height: var(--kemet-fab-size);
|
|
21
|
+
min-width: var(--kemet-fab-size);
|
|
22
|
+
max-width: var(--kemet-fab-size);
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: flex-start;
|
|
25
|
+
transition: all 0.4s ease;
|
|
26
|
+
border: none;
|
|
27
|
+
background-color: var(--kemet-fab-background-color, var(--kemet-color-primary));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:host([outlined]) button {
|
|
31
|
+
color: var(--kemet-fab-outlined-color, var(--kemet-color-primary));
|
|
32
|
+
border: var(--kemet-fab-outlined-border, 1px solid var(--kemet-color-primary));
|
|
33
|
+
background-color: transparent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:host([pill]) button {
|
|
37
|
+
border-radius: var(--kemet-fab-pill-radius, 10rem);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
button::before {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
top: 0;
|
|
44
|
+
left: 0;
|
|
45
|
+
right: 0;
|
|
46
|
+
bottom: 0;
|
|
47
|
+
z-index: -1;
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
transition: background-color 0.3s ease-in-out;
|
|
51
|
+
background-color: var(--kemet-fab-background-color, var(--kemet-color-primary));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:host([pill]) button::before {
|
|
55
|
+
border-radius: var(--kemet-fab-pill-radius, 10rem);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:host([outlined]) button::before {
|
|
59
|
+
border: var(--kemet-fab-outline-border, 1px solid var(--kemet-color-primary));
|
|
60
|
+
background-color: transparent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:host([expanded]) button {
|
|
64
|
+
max-width: 99rem;
|
|
65
|
+
padding: 0 1.35rem 0 0.25rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:host([disabled]) button {
|
|
69
|
+
opacity: 0.5;
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.icon {
|
|
74
|
+
width: var(--kemet-fab-size);
|
|
75
|
+
height: var(--kemet-fab-size);
|
|
76
|
+
display: flex;
|
|
77
|
+
flex: 0 0 auto;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.text {
|
|
83
|
+
color: var(--kemet-fab-color, var(--kemet-color-white));
|
|
84
|
+
z-index: 1;
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
pointer-events: none;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transition: opacity 0.2s ease-in-out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:host([outlined]) .text {
|
|
92
|
+
color: var(--kemet-fab-outlined-color, var(--kemet-color-primary));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
:host([expanded]) .text {
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static get properties() {
|
|
103
|
+
return {
|
|
104
|
+
/**
|
|
105
|
+
* The expanded state of the button
|
|
106
|
+
*/
|
|
107
|
+
expanded: {
|
|
108
|
+
type: Boolean,
|
|
109
|
+
reflect: true,
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* Outline style for a button
|
|
113
|
+
*/
|
|
114
|
+
outlined: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
reflect: true,
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Determines whether not a button is disabled
|
|
120
|
+
*/
|
|
121
|
+
disabled: {
|
|
122
|
+
type: Boolean,
|
|
123
|
+
reflect: true,
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* Displays the fab with rounded corners
|
|
127
|
+
*/
|
|
128
|
+
pill: {
|
|
129
|
+
type: Boolean,
|
|
130
|
+
reflect: true,
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* The distance, in pixels, where the fab should automatically expand
|
|
134
|
+
*/
|
|
135
|
+
expandPoint: {
|
|
136
|
+
type: Number,
|
|
137
|
+
attribute: 'expand-point',
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* The distance, in pixels, where the fab should automatically collapse
|
|
141
|
+
*/
|
|
142
|
+
collapsePoint: {
|
|
143
|
+
type: Number,
|
|
144
|
+
attribute: 'collapse-point',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
constructor() {
|
|
150
|
+
super();
|
|
151
|
+
|
|
152
|
+
this.outlined = false;
|
|
153
|
+
this.disabled = false;
|
|
154
|
+
this.expanded = false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
firstUpdated() {
|
|
158
|
+
// events
|
|
159
|
+
window.addEventListener('scroll', this.handleScroll.bind(this));
|
|
160
|
+
this.addEventListener('mouseover', this.handleMouseOver.bind(this));
|
|
161
|
+
this.addEventListener('mouseout', this.handleMouseOut.bind(this));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
render() {
|
|
165
|
+
return html`
|
|
166
|
+
<button class="button" part="button" ?disabled=${this.disabled} aria-disabled=${this.disabled ? 'true' : 'false'}>
|
|
167
|
+
<span class="icon" part="icon">
|
|
168
|
+
<slot name="icon"></slot>
|
|
169
|
+
</span>
|
|
170
|
+
<span class="text" part="text">
|
|
171
|
+
<slot></slot>
|
|
172
|
+
</span>
|
|
173
|
+
</button>
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
handleMouseOver() {
|
|
178
|
+
if (!this.disabled) {
|
|
179
|
+
this.expanded = true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
handleMouseOut() {
|
|
184
|
+
if (!this.disabled) {
|
|
185
|
+
this.expanded = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
handleScroll() {
|
|
190
|
+
if (window.scrollY > this.expandPoint) {
|
|
191
|
+
this.expanded = true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (window.scrollY < this.expandPoint) {
|
|
195
|
+
this.expanded = false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (window.scrollY > this.collapsePoint) {
|
|
199
|
+
this.expanded = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (window.scrollY < this.collapsePoint) {
|
|
203
|
+
this.expanded = true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// eslint-disable-next-line no-unused-expressions
|
|
209
|
+
customElements.get('kemet-fab') || customElements.define('kemet-fab', KemetFAB);
|
|
@@ -322,6 +322,13 @@ export default class KemetInput extends LitElement {
|
|
|
322
322
|
inputType: {
|
|
323
323
|
type: String,
|
|
324
324
|
},
|
|
325
|
+
/**
|
|
326
|
+
* Determines if the input is focused
|
|
327
|
+
*/
|
|
328
|
+
focused: {
|
|
329
|
+
type: Boolean,
|
|
330
|
+
reflect: true,
|
|
331
|
+
},
|
|
325
332
|
};
|
|
326
333
|
}
|
|
327
334
|
|
|
@@ -336,6 +343,7 @@ export default class KemetInput extends LitElement {
|
|
|
336
343
|
this.iconSize = 20;
|
|
337
344
|
this.validateOnBlur = false;
|
|
338
345
|
this.isPasswordVisible = false;
|
|
346
|
+
this.focused = false;
|
|
339
347
|
|
|
340
348
|
/**
|
|
341
349
|
* Used only for form reactive controller
|
|
@@ -349,6 +357,10 @@ export default class KemetInput extends LitElement {
|
|
|
349
357
|
this.field = this.closest('kemet-field');
|
|
350
358
|
this.form = this.closest('form');
|
|
351
359
|
this.slug = this.field ? this.field.slug : 'input';
|
|
360
|
+
|
|
361
|
+
if (this.field) {
|
|
362
|
+
this.field.addEventListener('fmc-password-status', event => this.handleStatus(event));
|
|
363
|
+
}
|
|
352
364
|
}
|
|
353
365
|
|
|
354
366
|
render() {
|
|
@@ -463,7 +475,7 @@ export default class KemetInput extends LitElement {
|
|
|
463
475
|
* @private
|
|
464
476
|
*/
|
|
465
477
|
handleFocus() {
|
|
466
|
-
this.
|
|
478
|
+
this.focused = true;
|
|
467
479
|
|
|
468
480
|
/**
|
|
469
481
|
* Fires when the input receives and loses focus
|
|
@@ -487,7 +499,7 @@ export default class KemetInput extends LitElement {
|
|
|
487
499
|
this.validity = this.input.validity;
|
|
488
500
|
}
|
|
489
501
|
|
|
490
|
-
this.
|
|
502
|
+
this.focused = false;
|
|
491
503
|
|
|
492
504
|
/**
|
|
493
505
|
* Fires when the input receives and loses focus
|
|
@@ -537,8 +549,7 @@ export default class KemetInput extends LitElement {
|
|
|
537
549
|
* Handles when the input receives input
|
|
538
550
|
* @private
|
|
539
551
|
*/
|
|
540
|
-
handleInput(
|
|
541
|
-
this.inputType = e.inputType;
|
|
552
|
+
handleInput() {
|
|
542
553
|
this.value = this.input.value;
|
|
543
554
|
|
|
544
555
|
/**
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { html, css, LitElement } from 'lit';
|
|
2
|
+
|
|
3
|
+
export default class KemetPassword extends LitElement {
|
|
4
|
+
static get styles() {
|
|
5
|
+
return [
|
|
6
|
+
css`
|
|
7
|
+
:host {
|
|
8
|
+
color: inherit;
|
|
9
|
+
display: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:host([show]) {
|
|
13
|
+
display: block;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
p {
|
|
17
|
+
margin: 1.6rem 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.status {
|
|
21
|
+
display: grid;
|
|
22
|
+
align-items: center;
|
|
23
|
+
grid-template-columns: 1fr auto;
|
|
24
|
+
line-height: 1;
|
|
25
|
+
margin-top: 0.8rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.status > span {
|
|
29
|
+
text-transform: capitalize;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.status--weak,
|
|
33
|
+
.status--better {
|
|
34
|
+
color: var(--kemet-color-error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.status--strong {
|
|
38
|
+
color: var(--kemet-color-success);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.indicator {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 0.4rem;
|
|
44
|
+
margin: 0;
|
|
45
|
+
list-style: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.indicator li {
|
|
49
|
+
width: 1.6rem;
|
|
50
|
+
height: 0.2rem;
|
|
51
|
+
background-color: var(--fds-color--gray2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.status--weak .indicator li:nth-child(1) {
|
|
55
|
+
background-color: var(--kemet-color-error);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.status--better .indicator li:nth-child(1),
|
|
59
|
+
.status--better .indicator li:nth-child(2) {
|
|
60
|
+
background-color: var(--kemet-color-error);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.status--strong .indicator li {
|
|
64
|
+
background-color: var(--kemet-color-success);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.rules {
|
|
68
|
+
color: var(--kemet-color-primary);
|
|
69
|
+
list-style: none;
|
|
70
|
+
font-size: 90%;
|
|
71
|
+
padding-left: 2rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.rules li {
|
|
75
|
+
position: relative;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.rules kemet-icon {
|
|
79
|
+
color: var(--kemet-color-success);
|
|
80
|
+
position: absolute;
|
|
81
|
+
left: -2.4rem;
|
|
82
|
+
}
|
|
83
|
+
`,
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static get properties() {
|
|
88
|
+
return {
|
|
89
|
+
/**
|
|
90
|
+
* An array of objects containing the rules the password must meet
|
|
91
|
+
*/
|
|
92
|
+
rules: {
|
|
93
|
+
type: Array,
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Controls the display of the component
|
|
97
|
+
*/
|
|
98
|
+
show: {
|
|
99
|
+
type: Boolean,
|
|
100
|
+
reflect: true,
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* The value of the input component
|
|
104
|
+
*/
|
|
105
|
+
value: {
|
|
106
|
+
type: String,
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* A message that is above the rules
|
|
110
|
+
*/
|
|
111
|
+
message: {
|
|
112
|
+
type: String,
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* The strength of the password. Weak | Better | Strong
|
|
116
|
+
*/
|
|
117
|
+
strength: {
|
|
118
|
+
type: String,
|
|
119
|
+
},
|
|
120
|
+
icon: {
|
|
121
|
+
type: String,
|
|
122
|
+
},
|
|
123
|
+
iconSize: {
|
|
124
|
+
type: Number,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
constructor() {
|
|
130
|
+
super();
|
|
131
|
+
|
|
132
|
+
// managed properties
|
|
133
|
+
this.rules = [
|
|
134
|
+
{ pattern: '(?=.{8,}$)', message: 'At least 8 characters long' },
|
|
135
|
+
{ pattern: '(?=.*[a-z])(?=.*[A-Z])', message: 'Uppercase and lowercase' },
|
|
136
|
+
{ pattern: '(?=.*[0-9])', message: 'At least one number (0-9)' },
|
|
137
|
+
];
|
|
138
|
+
this.message = 'Please make sure you meet all the requirements.';
|
|
139
|
+
this.icon = 'check2';
|
|
140
|
+
this.iconSize = 16;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
firstUpdated() {
|
|
144
|
+
// standard properties
|
|
145
|
+
this.field = this.closest('kemet-field');
|
|
146
|
+
this.input = this.field.querySelector('[slot="input"]');
|
|
147
|
+
|
|
148
|
+
// managed properties
|
|
149
|
+
this.slug = this.field.slug;
|
|
150
|
+
|
|
151
|
+
// events listeners
|
|
152
|
+
this.input.addEventListener('kemet-input-input', this.handleInput.bind(this));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
render() {
|
|
156
|
+
return html`
|
|
157
|
+
<div role="alert" aria-live="assertive">
|
|
158
|
+
<div class="status status--${this.strength}" part="status">
|
|
159
|
+
<span>${this.strength}</span>
|
|
160
|
+
<ul class="indicator" part="indicator">
|
|
161
|
+
<li></li>
|
|
162
|
+
<li></li>
|
|
163
|
+
<li></li>
|
|
164
|
+
</ul>
|
|
165
|
+
</div>
|
|
166
|
+
<p class="message" part="message">${this.message}</p>
|
|
167
|
+
<ul class="rules" part="rules">
|
|
168
|
+
${this.makeRules()}
|
|
169
|
+
</ul>
|
|
170
|
+
</div>
|
|
171
|
+
`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Makes the list of rules
|
|
176
|
+
* @private
|
|
177
|
+
* @returns {templateResults} the criteria rules
|
|
178
|
+
*/
|
|
179
|
+
makeRules() {
|
|
180
|
+
if (this.input) {
|
|
181
|
+
const rules = this.rules.map((rule, index) => {
|
|
182
|
+
const regExp = new RegExp(rule.pattern);
|
|
183
|
+
const meetsCriteria = regExp.test(this.input.value);
|
|
184
|
+
this.rules[index].meetsCriteria = meetsCriteria;
|
|
185
|
+
return html`<li>${this.makeCheckIcon(meetsCriteria)} ${rule.message}</li>`;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return rules;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Makes the check icon if the criteria has been met
|
|
196
|
+
* @param {boolean} meetsCriteria
|
|
197
|
+
* @private
|
|
198
|
+
* @returns {templateResults} an icon component
|
|
199
|
+
*/
|
|
200
|
+
makeCheckIcon(meetsCriteria) {
|
|
201
|
+
if (meetsCriteria) {
|
|
202
|
+
return html`<kemet-icon icon=${this.icon} size=${this.iconSize}></kemet-icon>`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Handles the kemet-input-input event
|
|
210
|
+
* @param {*} event
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
handleInput(event) {
|
|
214
|
+
this.value = event.detail;
|
|
215
|
+
this.setStrength();
|
|
216
|
+
this.visibility();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Determines the strength of the password
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
setStrength() {
|
|
224
|
+
// We use a setTimeout here because makeRules adds the meetsCriteria
|
|
225
|
+
// property to the rules. We need that to be set by render() before
|
|
226
|
+
// setting the Strength because the percentage calc is based on it
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
const totalRules = this.rules.length;
|
|
229
|
+
const metRules = this.rules.filter(rule => rule.meetsCriteria).length;
|
|
230
|
+
const metRulesPercentage = Math.round((metRules / totalRules) * 100) / 100;
|
|
231
|
+
|
|
232
|
+
if (metRulesPercentage <= 0.33) {
|
|
233
|
+
this.strength = 'weak';
|
|
234
|
+
this.status = 'error';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (metRulesPercentage > 0.33 && metRulesPercentage <= 0.67) {
|
|
238
|
+
this.strength = 'better';
|
|
239
|
+
this.status = 'error';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (metRulesPercentage > 0.67) {
|
|
243
|
+
this.strength = 'strong';
|
|
244
|
+
this.status = 'success';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Fires when there's a change in status.
|
|
249
|
+
*/
|
|
250
|
+
this.dispatchEvent(
|
|
251
|
+
new CustomEvent('kemet-password-status', {
|
|
252
|
+
bubbles: true,
|
|
253
|
+
composed: true,
|
|
254
|
+
detail: {
|
|
255
|
+
status: this.status,
|
|
256
|
+
validity: { meetsPasswordCriteria: this.status === 'success' },
|
|
257
|
+
element: this,
|
|
258
|
+
},
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
}, 1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Determines whether to show or hide the component
|
|
266
|
+
* @private
|
|
267
|
+
*/
|
|
268
|
+
visibility() {
|
|
269
|
+
if (this.value.length > 0) {
|
|
270
|
+
this.show = true;
|
|
271
|
+
} else {
|
|
272
|
+
this.show = false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// eslint-disable-next-line no-unused-expressions
|
|
278
|
+
customElements.get('kemet-password') || customElements.define('kemet-password', KemetPassword);
|