@dile/ui 2.26.0 → 2.27.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/components/button/button.spec.js +48 -0
- package/components/otp-input/index.js +1 -0
- package/components/otp-input/otp-input.js +3 -0
- package/components/otp-input/src/DileOtpInput.js +326 -0
- package/components/toast/src/DileToast.js +32 -1
- package/components/toast/src/DileToastItem.js +35 -1
- package/lib/otp/sanitizeOtpValue.js +4 -0
- package/lib/otp/sanitizeOtpValue.test.js +24 -0
- package/package.json +3 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import './button.js';
|
|
3
|
+
|
|
4
|
+
describe('dile-button', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
async function renderButton(html) {
|
|
10
|
+
document.body.innerHTML = html;
|
|
11
|
+
const el = document.body.querySelector('dile-button');
|
|
12
|
+
await el.updateComplete;
|
|
13
|
+
return el;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
it('renders the slotted content inside a native button', async () => {
|
|
17
|
+
const el = await renderButton('<dile-button>Click me</dile-button>');
|
|
18
|
+
|
|
19
|
+
expect(el.shadowRoot.querySelector('button')).toBeTruthy();
|
|
20
|
+
expect(el.textContent.trim()).toBe('Click me');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reflects the disabled property onto the native button', async () => {
|
|
24
|
+
const el = await renderButton('<dile-button disabled>Click me</dile-button>');
|
|
25
|
+
|
|
26
|
+
expect(el.shadowRoot.querySelector('button').disabled).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('dispatches a click event when enabled', async () => {
|
|
30
|
+
const el = await renderButton('<dile-button>Click me</dile-button>');
|
|
31
|
+
let clicked = false;
|
|
32
|
+
el.addEventListener('click', () => { clicked = true; });
|
|
33
|
+
|
|
34
|
+
el.shadowRoot.querySelector('button').click();
|
|
35
|
+
|
|
36
|
+
expect(clicked).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('shows a spinner icon while loading', async () => {
|
|
40
|
+
const el = await renderButton('<dile-button>Save</dile-button>');
|
|
41
|
+
expect(el.shadowRoot.querySelector('dile-spinner-icon')).toBeNull();
|
|
42
|
+
|
|
43
|
+
el.loading = true;
|
|
44
|
+
await el.updateComplete;
|
|
45
|
+
|
|
46
|
+
expect(el.shadowRoot.querySelector('dile-spinner-icon')).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DileOtpInput } from './src/DileOtpInput.js';
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { DileEmmitChange } from '../../../mixins/form/index.js';
|
|
3
|
+
import { messageStyles } from '../../input/index.js';
|
|
4
|
+
import { sanitizeOtpValue } from '../../../lib/otp/sanitizeOtpValue.js';
|
|
5
|
+
|
|
6
|
+
export class DileOtpInput extends DileEmmitChange(LitElement) {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fired when user press enter key.
|
|
10
|
+
*
|
|
11
|
+
* @event enter-pressed
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fired when value changes, alongside the generic `element-changed` event.
|
|
16
|
+
*
|
|
17
|
+
* @event dile-otp-input-changed
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Fired once, when the last empty box has just been filled and the code is complete.
|
|
22
|
+
*
|
|
23
|
+
* @event dile-otp-input-completed
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
static get properties() {
|
|
27
|
+
return {
|
|
28
|
+
/** Label to the element */
|
|
29
|
+
label: { type: String },
|
|
30
|
+
|
|
31
|
+
/** Number of characters (boxes) of the code */
|
|
32
|
+
length: { type: Number },
|
|
33
|
+
|
|
34
|
+
/** Set initial value to the input. This property syncs to each box value */
|
|
35
|
+
value: { type: String },
|
|
36
|
+
|
|
37
|
+
/** Name for this input field */
|
|
38
|
+
name: { type: String },
|
|
39
|
+
|
|
40
|
+
/** Disable the input field */
|
|
41
|
+
disabled: { type: Boolean },
|
|
42
|
+
|
|
43
|
+
/** ReadOnly attribute */
|
|
44
|
+
readonly: { type: Boolean },
|
|
45
|
+
|
|
46
|
+
/** Has error on this input field */
|
|
47
|
+
errored: { type: Boolean },
|
|
48
|
+
|
|
49
|
+
/** Message Displayed */
|
|
50
|
+
message: { type: String },
|
|
51
|
+
|
|
52
|
+
/** Hide errors on input */
|
|
53
|
+
hideErrorOnInput: { type: Boolean },
|
|
54
|
+
|
|
55
|
+
/** Set the application focus to this the input component after the initialization */
|
|
56
|
+
focusOnStart: { type: Boolean },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static get formAssociated() {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
constructor() {
|
|
65
|
+
super();
|
|
66
|
+
this.label = '';
|
|
67
|
+
this.value = '';
|
|
68
|
+
this.name = '';
|
|
69
|
+
this.length = 6;
|
|
70
|
+
this.disabled = false;
|
|
71
|
+
this.internals = this.attachInternals();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
firstUpdated() {
|
|
75
|
+
if (this.focusOnStart) {
|
|
76
|
+
this.focus();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
updated(changedProperties) {
|
|
81
|
+
if (changedProperties.has('value')) {
|
|
82
|
+
this.emmitChange();
|
|
83
|
+
this.internals.setFormValue(this.value);
|
|
84
|
+
this.dispatchEvent(new CustomEvent('dile-otp-input-changed', {
|
|
85
|
+
bubbles: true,
|
|
86
|
+
composed: true,
|
|
87
|
+
detail: {
|
|
88
|
+
name: this.name,
|
|
89
|
+
value: this.value
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
const previousValue = changedProperties.get('value') || '';
|
|
94
|
+
if (this.value.length === this.length && previousValue.length !== this.length) {
|
|
95
|
+
this.dispatchEvent(new CustomEvent('dile-otp-input-completed', {
|
|
96
|
+
bubbles: true,
|
|
97
|
+
composed: true,
|
|
98
|
+
detail: {
|
|
99
|
+
name: this.name,
|
|
100
|
+
value: this.value
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static get styles() {
|
|
108
|
+
return [
|
|
109
|
+
messageStyles,
|
|
110
|
+
css`
|
|
111
|
+
* {
|
|
112
|
+
box-sizing: border-box;
|
|
113
|
+
}
|
|
114
|
+
:host {
|
|
115
|
+
display: block;
|
|
116
|
+
margin-bottom: 10px;
|
|
117
|
+
}
|
|
118
|
+
main {
|
|
119
|
+
width: var(--dile-otp-input-section-width, auto);
|
|
120
|
+
}
|
|
121
|
+
label {
|
|
122
|
+
display: block;
|
|
123
|
+
margin-bottom: var(--dile-otp-input-label-margin-bottom, 4px);
|
|
124
|
+
font-size: var(--dile-otp-input-label-font-size, 1em);
|
|
125
|
+
color: var(--dile-otp-input-label-color, #59e);
|
|
126
|
+
font-weight: var(--dile-otp-input-label-font-weight, normal);
|
|
127
|
+
}
|
|
128
|
+
section.for-input {
|
|
129
|
+
display: flex;
|
|
130
|
+
gap: var(--dile-otp-input-gap, 8px);
|
|
131
|
+
}
|
|
132
|
+
input {
|
|
133
|
+
box-sizing: border-box;
|
|
134
|
+
width: var(--dile-otp-input-box-size, 2.5em);
|
|
135
|
+
height: var(--dile-otp-input-box-size, 2.5em);
|
|
136
|
+
padding: 0;
|
|
137
|
+
text-align: center;
|
|
138
|
+
border-radius: var(--dile-otp-input-border-radius, 5px);
|
|
139
|
+
border: var(--dile-otp-input-border-width, 1px) solid var(--dile-otp-input-border-color, #888);
|
|
140
|
+
font-size: var(--dile-otp-input-font-size, 1.25em);
|
|
141
|
+
background-color: var(--dile-otp-input-background-color, #fff);
|
|
142
|
+
color: var(--dile-otp-input-color, #303030);
|
|
143
|
+
}
|
|
144
|
+
input:focus {
|
|
145
|
+
outline: none;
|
|
146
|
+
border-color: var(--dile-otp-input-focus-border-color, #6af);
|
|
147
|
+
}
|
|
148
|
+
input:disabled {
|
|
149
|
+
background-color: #f5f5f5;
|
|
150
|
+
border-color: var(--dile-otp-input-disabled-border-color, #eee);
|
|
151
|
+
}
|
|
152
|
+
.errored {
|
|
153
|
+
border-color: var(--dile-otp-input-error-border-color, #c00);
|
|
154
|
+
background-color: var(--dile-otp-input-error-background-color, var(--dile-otp-input-background-color, #fff));
|
|
155
|
+
}
|
|
156
|
+
`
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
render() {
|
|
161
|
+
return html`
|
|
162
|
+
<main>
|
|
163
|
+
${this.label
|
|
164
|
+
? html`<label>${this.label}</label>`
|
|
165
|
+
: ''}
|
|
166
|
+
<section class="for-input">
|
|
167
|
+
${this.boxesChars.map((char, index) => html`
|
|
168
|
+
<input
|
|
169
|
+
type="text"
|
|
170
|
+
inputmode="numeric"
|
|
171
|
+
pattern="[0-9]*"
|
|
172
|
+
maxlength="1"
|
|
173
|
+
autocomplete="${index === 0 ? 'one-time-code' : 'off'}"
|
|
174
|
+
?disabled="${this.disabled}"
|
|
175
|
+
?readonly="${this.readonly}"
|
|
176
|
+
.value="${char}"
|
|
177
|
+
class="${this.errored ? 'errored' : ''}"
|
|
178
|
+
@input="${(e) => this._onInput(e, index)}"
|
|
179
|
+
@keydown="${(e) => this._onKeydown(e, index)}"
|
|
180
|
+
@keypress="${this._lookForEnter}"
|
|
181
|
+
@paste="${(e) => this._onPaste(e)}"
|
|
182
|
+
@focus="${this._onFocus}"
|
|
183
|
+
/>
|
|
184
|
+
`)}
|
|
185
|
+
</section>
|
|
186
|
+
${this.messageTemplate}
|
|
187
|
+
</main>
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get messageTemplate() {
|
|
192
|
+
return html`
|
|
193
|
+
${this.message
|
|
194
|
+
? html`<div class="message ${this.errored ? 'errored-msg' : ''}"><span>${this.message}</span></div>`
|
|
195
|
+
: ''
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The current value split into one character per box, padded with empty strings.
|
|
202
|
+
*/
|
|
203
|
+
get boxesChars() {
|
|
204
|
+
return Array.from({ length: this.length }, (_, index) => this.value.charAt(index));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The rendered box input elements, in order.
|
|
209
|
+
*/
|
|
210
|
+
get boxes() {
|
|
211
|
+
return Array.from(this.shadowRoot.querySelectorAll('input'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_setChar(index, char) {
|
|
215
|
+
const chars = this.boxesChars;
|
|
216
|
+
chars[index] = char;
|
|
217
|
+
this.value = chars.join('');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_onInput(e, index) {
|
|
221
|
+
const raw = e.target.value;
|
|
222
|
+
|
|
223
|
+
// Autofill (e.g. iOS/Android SMS autofill) can drop the whole code into a single box
|
|
224
|
+
if (raw.length > 1) {
|
|
225
|
+
const digits = sanitizeOtpValue(raw, this.length);
|
|
226
|
+
if (digits) {
|
|
227
|
+
if (this.hideErrorOnInput && this.errored) {
|
|
228
|
+
this.clearError();
|
|
229
|
+
}
|
|
230
|
+
this.value = digits;
|
|
231
|
+
this._focusBox(Math.min(digits.length, this.length - 1));
|
|
232
|
+
} else {
|
|
233
|
+
e.target.value = this.boxesChars[index];
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const digit = raw.replace(/\D/g, '');
|
|
239
|
+
if (!digit && raw !== '') {
|
|
240
|
+
e.target.value = this.boxesChars[index];
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this.hideErrorOnInput && this.errored) {
|
|
245
|
+
this.clearError();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this._setChar(index, digit);
|
|
249
|
+
|
|
250
|
+
if (digit && index < this.length - 1) {
|
|
251
|
+
this._focusBox(index + 1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_onKeydown(e, index) {
|
|
256
|
+
if (e.key === 'Backspace' && !this.boxesChars[index] && index > 0) {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
this._focusBox(index - 1);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (e.key === 'ArrowLeft' && index > 0) {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
this._focusBox(index - 1);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (e.key === 'ArrowRight' && index < this.length - 1) {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
this._focusBox(index + 1);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (e.key.length === 1 && !/[0-9]/.test(e.key) && !e.ctrlKey && !e.metaKey) {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_onPaste(e) {
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
const pasted = (e.clipboardData || window.clipboardData).getData('text');
|
|
279
|
+
const digits = sanitizeOtpValue(pasted, this.length);
|
|
280
|
+
if (!digits) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (this.hideErrorOnInput && this.errored) {
|
|
284
|
+
this.clearError();
|
|
285
|
+
}
|
|
286
|
+
this.value = digits;
|
|
287
|
+
this._focusBox(Math.min(digits.length, this.length - 1));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_onFocus(e) {
|
|
291
|
+
e.target.select();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
_focusBox(index) {
|
|
295
|
+
const input = this.boxes[index];
|
|
296
|
+
if (input) {
|
|
297
|
+
input.focus();
|
|
298
|
+
input.select();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Private method to dispatch events on enter key pressed
|
|
304
|
+
*/
|
|
305
|
+
_lookForEnter(e) {
|
|
306
|
+
let keycode = (e.keyCode ? e.keyCode : e.which);
|
|
307
|
+
if (keycode == '13') {
|
|
308
|
+
this.dispatchEvent(new CustomEvent('enter-pressed'));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
clearError() {
|
|
313
|
+
this.errored = false;
|
|
314
|
+
this.message = '';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
focus() {
|
|
318
|
+
const emptyIndex = this.boxesChars.findIndex((char) => !char);
|
|
319
|
+
this._focusBox(emptyIndex === -1 ? this.length - 1 : emptyIndex);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
clear() {
|
|
323
|
+
this.value = '';
|
|
324
|
+
this.focus();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
2
3
|
|
|
3
4
|
export class DileToast extends LitElement {
|
|
4
5
|
static get properties() {
|
|
@@ -7,6 +8,8 @@ export class DileToast extends LitElement {
|
|
|
7
8
|
messages: { type: Array },
|
|
8
9
|
/* Miliseconds the feedback message remains on the screen */
|
|
9
10
|
duration: { type: Number },
|
|
11
|
+
/* If true, shows a close icon on each toast so the user can dismiss it manually */
|
|
12
|
+
showCloseIcon: { type: Boolean },
|
|
10
13
|
};
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -15,6 +18,8 @@ export class DileToast extends LitElement {
|
|
|
15
18
|
this.messages = [];
|
|
16
19
|
this.duration = 3000;
|
|
17
20
|
this.cleanTimeout = false;
|
|
21
|
+
this.showCloseIcon = false;
|
|
22
|
+
this._messageId = 0;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
static get styles() {
|
|
@@ -31,11 +36,15 @@ export class DileToast extends LitElement {
|
|
|
31
36
|
|
|
32
37
|
render() {
|
|
33
38
|
return html`
|
|
34
|
-
${
|
|
39
|
+
${repeat(
|
|
40
|
+
this.messages,
|
|
41
|
+
(msg) => msg.id,
|
|
35
42
|
(msg) => html`
|
|
36
43
|
<dile-toast-item
|
|
37
44
|
.msg="${msg}"
|
|
45
|
+
.showCloseIcon="${this.showCloseIcon}"
|
|
38
46
|
duration="${this.duration}"
|
|
47
|
+
@dile-toast-item-closed="${(e) => this._closeMessage(e.detail.id)}"
|
|
39
48
|
></dile-toast-item>
|
|
40
49
|
`
|
|
41
50
|
)}
|
|
@@ -51,6 +60,7 @@ export class DileToast extends LitElement {
|
|
|
51
60
|
this.messages = [
|
|
52
61
|
...this.messages,
|
|
53
62
|
{
|
|
63
|
+
id: ++this._messageId,
|
|
54
64
|
text,
|
|
55
65
|
toastType,
|
|
56
66
|
hidden: false,
|
|
@@ -61,6 +71,27 @@ export class DileToast extends LitElement {
|
|
|
61
71
|
this._programMessageHide();
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Closes a specific message before its automatic duration elapses, triggered by the user
|
|
76
|
+
*/
|
|
77
|
+
_closeMessage(id) {
|
|
78
|
+
const message = this.messages.find((item) => item.id === id);
|
|
79
|
+
if (!message || message.hidden) return;
|
|
80
|
+
this.messages = this.messages.map((item) =>
|
|
81
|
+
item.id === id ? { ...item, hidden: true } : item
|
|
82
|
+
);
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
this.messages = this.messages.filter((item) => item.id !== id);
|
|
85
|
+
}, 500);
|
|
86
|
+
this.dispatchEvent(
|
|
87
|
+
new CustomEvent("dile-toast-closed-by-user", {
|
|
88
|
+
bubbles: true,
|
|
89
|
+
composed: true,
|
|
90
|
+
detail: { message },
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
64
95
|
/**
|
|
65
96
|
* Programs the hiding of the last message, after it's duration time
|
|
66
97
|
*/
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { clearIcon } from "@dile/icons/index.js";
|
|
3
|
+
import "../../icon/icon.js";
|
|
2
4
|
|
|
3
5
|
export class DileToastItem extends LitElement {
|
|
4
6
|
static get properties() {
|
|
5
7
|
return {
|
|
6
8
|
msg: { type: Object },
|
|
9
|
+
showCloseIcon: { type: Boolean },
|
|
7
10
|
};
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -15,6 +18,7 @@ export class DileToastItem extends LitElement {
|
|
|
15
18
|
hidden: false,
|
|
16
19
|
opening: true,
|
|
17
20
|
};
|
|
21
|
+
this.showCloseIcon = false;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
static get styles() {
|
|
@@ -26,6 +30,9 @@ export class DileToastItem extends LitElement {
|
|
|
26
30
|
display: block;
|
|
27
31
|
}
|
|
28
32
|
div {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
29
36
|
color: var(--dile-toast-text-color, #fff);
|
|
30
37
|
padding: var(--dile-toast-padding, 10px 15px);
|
|
31
38
|
margin-top: 10px;
|
|
@@ -40,6 +47,16 @@ export class DileToastItem extends LitElement {
|
|
|
40
47
|
font-weight: var(--dile-toast-font-weight, normal);
|
|
41
48
|
border-radius: var(--dile-toast-border-radius, 0);
|
|
42
49
|
}
|
|
50
|
+
.text {
|
|
51
|
+
flex-grow: 1;
|
|
52
|
+
}
|
|
53
|
+
.closeicon {
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
margin-left: 10px;
|
|
57
|
+
--dile-icon-color: var(--dile-toast-close-icon-color, var(--dile-toast-text-color, #fff));
|
|
58
|
+
--dile-icon-size: var(--dile-toast-close-icon-size, 16px);
|
|
59
|
+
}
|
|
43
60
|
.hidden {
|
|
44
61
|
top: 20px;
|
|
45
62
|
opacity: 0;
|
|
@@ -68,11 +85,28 @@ export class DileToastItem extends LitElement {
|
|
|
68
85
|
? "opening"
|
|
69
86
|
: ""} ${this.getClassType(this.msg.toastType)}"
|
|
70
87
|
>
|
|
71
|
-
|
|
88
|
+
<span class="text">${this.msg.text}</span>
|
|
89
|
+
${this.showCloseIcon
|
|
90
|
+
? html`<dile-icon
|
|
91
|
+
.icon="${clearIcon}"
|
|
92
|
+
class="closeicon"
|
|
93
|
+
@click="${this.closeByUser}"
|
|
94
|
+
></dile-icon>`
|
|
95
|
+
: ""}
|
|
72
96
|
</div>
|
|
73
97
|
`;
|
|
74
98
|
}
|
|
75
99
|
|
|
100
|
+
closeByUser() {
|
|
101
|
+
this.dispatchEvent(
|
|
102
|
+
new CustomEvent("dile-toast-item-closed", {
|
|
103
|
+
bubbles: true,
|
|
104
|
+
composed: true,
|
|
105
|
+
detail: { id: this.msg.id, msg: this.msg },
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
firstUpdated() {
|
|
77
111
|
setTimeout(() => {
|
|
78
112
|
this.msg.opening = false;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { sanitizeOtpValue } from './sanitizeOtpValue.js';
|
|
4
|
+
|
|
5
|
+
test('returns empty string for empty input', () => {
|
|
6
|
+
assert.equal(sanitizeOtpValue('', 6), '');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('strips non-digit characters', () => {
|
|
10
|
+
assert.equal(sanitizeOtpValue('12 34-56', 6), '123456');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('truncates to the given length', () => {
|
|
14
|
+
assert.equal(sanitizeOtpValue('1234567890', 6), '123456');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('returns empty string for null or undefined', () => {
|
|
18
|
+
assert.equal(sanitizeOtpValue(null, 6), '');
|
|
19
|
+
assert.equal(sanitizeOtpValue(undefined, 6), '');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('keeps digits shorter than length untouched', () => {
|
|
23
|
+
assert.equal(sanitizeOtpValue('12', 6), '12');
|
|
24
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.1",
|
|
4
4
|
"description": "UI Core components from dile-components.",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
9
|
},
|
|
@@ -26,5 +27,5 @@
|
|
|
26
27
|
"publishConfig": {
|
|
27
28
|
"access": "public"
|
|
28
29
|
},
|
|
29
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "fec5662d02ad55763620b92b5748e45217f31b11"
|
|
30
31
|
}
|