@descope-ui/descope-user-passkeys 3.11.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/CHANGELOG.md +12 -0
- package/e2e/descope-user-passkeys.spec.ts +191 -0
- package/package.json +37 -0
- package/project.json +8 -0
- package/src/component/UserPasskeysClass.js +470 -0
- package/src/component/helpers.js +171 -0
- package/src/component/icons/checkmark.svg +3 -0
- package/src/component/icons/unknown-device-dark.svg +3 -0
- package/src/component/icons/unknown-device-light.svg +3 -0
- package/src/component/index.js +12 -0
- package/src/component/providers/1password-dark.svg +1 -0
- package/src/component/providers/1password-light.svg +1 -0
- package/src/component/providers/apple-dark.svg +1 -0
- package/src/component/providers/apple-light.svg +1 -0
- package/src/component/providers/bitwarden-dark.svg +1 -0
- package/src/component/providers/bitwarden-light.svg +1 -0
- package/src/component/providers/chrome-dark.svg +1 -0
- package/src/component/providers/chrome-light.svg +1 -0
- package/src/component/providers/dashlane-dark.svg +1 -0
- package/src/component/providers/dashlane-light.svg +1 -0
- package/src/component/providers/edge-dark.svg +46 -0
- package/src/component/providers/edge-light.svg +46 -0
- package/src/component/providers/enpass-dark.svg +10 -0
- package/src/component/providers/enpass-light.svg +10 -0
- package/src/component/providers/google-dark.svg +1 -0
- package/src/component/providers/google-light.svg +1 -0
- package/src/component/providers/keepassxc-dark.svg +1 -0
- package/src/component/providers/keepassxc-light.svg +1 -0
- package/src/component/providers/keeper-dark.svg +1 -0
- package/src/component/providers/keeper-light.svg +1 -0
- package/src/component/providers/key-dark.svg +1 -0
- package/src/component/providers/key-light.svg +1 -0
- package/src/component/providers/lastpass-dark.svg +1 -0
- package/src/component/providers/lastpass-light.svg +1 -0
- package/src/component/providers/nordpass-dark.svg +11 -0
- package/src/component/providers/nordpass-light.svg +11 -0
- package/src/component/providers/other-dark.svg +1 -0
- package/src/component/providers/other-light.svg +1 -0
- package/src/component/providers/phone-dark.svg +1 -0
- package/src/component/providers/phone-light.svg +1 -0
- package/src/component/providers/protonpass-dark.svg +1 -0
- package/src/component/providers/protonpass-light.svg +1 -0
- package/src/component/providers/unknown-dark.svg +1 -0
- package/src/component/providers/unknown-device-dark.svg +3 -0
- package/src/component/providers/unknown-device-light.svg +3 -0
- package/src/component/providers/unknown-light.svg +1 -0
- package/src/component/providers/windows-dark.svg +1 -0
- package/src/component/providers/windows-light.svg +1 -0
- package/src/theme.js +44 -0
- package/stories/descope-user-passkeys.stories.js +175 -0
- package/stories/icons/button-icon.svg +3 -0
- package/stories/icons/method-icon.svg +3 -0
- package/stories/icons/remove-icon.svg +3 -0
- package/testDriver/userPasskeysTestDriver.ts +10 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { compose } from '@descope-ui/common/utils';
|
|
2
|
+
import {
|
|
3
|
+
forwardAttrs,
|
|
4
|
+
getComponentName,
|
|
5
|
+
injectStyle,
|
|
6
|
+
} from '@descope-ui/common/components-helpers';
|
|
7
|
+
import {
|
|
8
|
+
createStyleMixin,
|
|
9
|
+
draggableMixin,
|
|
10
|
+
componentNameValidationMixin,
|
|
11
|
+
createDynamicDataMixin,
|
|
12
|
+
} from '@descope-ui/common/components-mixins';
|
|
13
|
+
import { createBaseClass } from '@descope-ui/common/base-classes';
|
|
14
|
+
import { ListClass } from '@descope-ui/descope-list/class';
|
|
15
|
+
import { ListItemClass } from '@descope-ui/descope-list-item/class';
|
|
16
|
+
import { getIcon, parseDate, populateRemoveButton, sortFn } from './helpers';
|
|
17
|
+
import checkmark from './icons/checkmark.svg';
|
|
18
|
+
import { LinkClass } from '@descope-ui/descope-link';
|
|
19
|
+
|
|
20
|
+
export const componentName = getComponentName('user-passkeys');
|
|
21
|
+
|
|
22
|
+
const itemRenderer = ({ id, name, createdAt, passkeyType }, _, ref) => {
|
|
23
|
+
const { iconSrc, iconSrcDark } = getIcon(passkeyType);
|
|
24
|
+
|
|
25
|
+
const createdAtLabel =
|
|
26
|
+
createdAt && ref.createdAtLabel ? `${ref.createdAtLabel} ` : '';
|
|
27
|
+
|
|
28
|
+
const createdAtDate = parseDate(createdAt, ref.format);
|
|
29
|
+
|
|
30
|
+
const template = document.createElement('template');
|
|
31
|
+
|
|
32
|
+
template.innerHTML = `
|
|
33
|
+
<descope-list-item>
|
|
34
|
+
<div class="content">
|
|
35
|
+
<div class="main">
|
|
36
|
+
<span class="item">
|
|
37
|
+
<descope-icon class="item-icon" src="${iconSrc}" src-dark="${iconSrcDark}"></descope-icon>
|
|
38
|
+
<descope-text class="item-name" variant="body1" mode="primary"></descope-text>
|
|
39
|
+
</span>
|
|
40
|
+
<span class="item-panel">
|
|
41
|
+
${ref.allowRemove ? `<descope-link variant="text" mode="primary" size="md" ellipsis="true" data-action="remove-passkey" class="remove-button"></descope-link>` : `<descope-icon src="${checkmark}"></descope-icon>`}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="meta">
|
|
45
|
+
<descope-text variant="body2" mode="primary">${createdAtLabel}</descope-text>
|
|
46
|
+
<descope-text class="date" variant="body2" mode="primary">
|
|
47
|
+
${createdAtDate}
|
|
48
|
+
</descope-text>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</descope-list-item>
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
// we return a template instead of returning a string so we can avoid XSS on passkey name
|
|
55
|
+
template.content.querySelector('.item-name').textContent = name;
|
|
56
|
+
|
|
57
|
+
if (ref.allowRemove) {
|
|
58
|
+
populateRemoveButton(template, id, ref);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return template;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const BaseClass = createBaseClass({
|
|
65
|
+
componentName,
|
|
66
|
+
baseSelector: ListClass.componentName,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
class RawUserPasskeysClass extends BaseClass {
|
|
70
|
+
static get observedAttributes() {
|
|
71
|
+
return [
|
|
72
|
+
'label',
|
|
73
|
+
'add-passkey-button-label',
|
|
74
|
+
'remove-passkey-button-label',
|
|
75
|
+
'empty',
|
|
76
|
+
].concat(super.observedAttributes);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor() {
|
|
80
|
+
super();
|
|
81
|
+
|
|
82
|
+
this.attachShadow({ mode: 'open' }).innerHTML = `
|
|
83
|
+
<div class="header">
|
|
84
|
+
<div class="method">
|
|
85
|
+
<slot name="method-icon"></slot>
|
|
86
|
+
<descope-text class="method-label" st-text-align="auto" data-id="label-text" variant="body1" mode="primary"></descope-text>
|
|
87
|
+
</div>
|
|
88
|
+
<span class="button">
|
|
89
|
+
<descope-button class="add-passkey-button" size="sm" variant="link" mode="primary">
|
|
90
|
+
<slot name="button-icon"></slot>
|
|
91
|
+
</descope-button>
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<descope-list>
|
|
96
|
+
<slot name="empty-state" slot="empty-state"></slot>
|
|
97
|
+
</descope-list>
|
|
98
|
+
|
|
99
|
+
<slot name="remove-button" class="hidden"></slot>
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
this.methodLabel = this.shadowRoot.querySelector('.method-label');
|
|
103
|
+
this.addPasskeyButton = this.shadowRoot.querySelector(
|
|
104
|
+
'.add-passkey-button',
|
|
105
|
+
);
|
|
106
|
+
this.appsList = this.shadowRoot.querySelector('descope-list');
|
|
107
|
+
this.removeButton = this.shadowRoot.querySelector(
|
|
108
|
+
'slot[name="remove-button"]',
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
injectStyle(
|
|
112
|
+
`
|
|
113
|
+
:host {
|
|
114
|
+
display: inline-block;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.header {
|
|
118
|
+
display: flex;
|
|
119
|
+
justify-content: space-between;
|
|
120
|
+
align-items: center;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.header .method {
|
|
124
|
+
display: flex;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.button {
|
|
128
|
+
min-width: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.add-passkey-button {
|
|
132
|
+
align-self: center;
|
|
133
|
+
max-width: 100%;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.add-passkey-button > span {
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
text-overflow: ellipsis;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
slot[name="method-icon"] {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.descope-list-item {
|
|
148
|
+
min-width: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.content {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
width: 100%;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.main {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: space-between;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.item-panel {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
max-width: 75%;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
align-items: center;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.item {
|
|
173
|
+
display: flex;
|
|
174
|
+
min-width: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.item-icon {
|
|
178
|
+
flex-shrink: 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.meta {
|
|
182
|
+
display: flex;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.date {
|
|
186
|
+
min-width: fit-content;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
descope-text {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
min-width: 0;
|
|
193
|
+
}
|
|
194
|
+
descope-text::part(text-wrapper) {
|
|
195
|
+
text-overflow: ellipsis;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
white-space: nowrap;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
descope-link {
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
}
|
|
203
|
+
descope-link.remove-button {
|
|
204
|
+
width: 100%;
|
|
205
|
+
}
|
|
206
|
+
descope-link.remove-button::part(wrapper) {
|
|
207
|
+
display: flex;
|
|
208
|
+
width: 100%;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.hidden {
|
|
212
|
+
display: none
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
`,
|
|
216
|
+
this,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
init() {
|
|
221
|
+
super.init?.();
|
|
222
|
+
|
|
223
|
+
this.appsList.itemRenderer = itemRenderer;
|
|
224
|
+
|
|
225
|
+
this.appsList.addEventListener('click', this.onRemoveClick.bind(this));
|
|
226
|
+
this.addPasskeyButton.addEventListener('click', this.onAddClick.bind(this));
|
|
227
|
+
|
|
228
|
+
forwardAttrs(this.appsList, this, { includeAttrs: ['empty'] });
|
|
229
|
+
|
|
230
|
+
this.removeButton.addEventListener('slotchange', () => {
|
|
231
|
+
this.data = [...this.data]; // re-renders list
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// apply default labels
|
|
235
|
+
this.onLabelChange();
|
|
236
|
+
this.updateButtonLabel(this.addPasskeyButton, this.addPasskeyButtonLabel);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onAddClick() {
|
|
240
|
+
if (this.readOnly) return;
|
|
241
|
+
|
|
242
|
+
this.dispatchEvent(
|
|
243
|
+
new CustomEvent('add-passkey-clicked', {
|
|
244
|
+
bubbles: true,
|
|
245
|
+
detail: { action: 'add-passkey' },
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
onRemoveClick(e) {
|
|
251
|
+
if (this.readOnly) return;
|
|
252
|
+
|
|
253
|
+
const target = e.target.closest('[data-passkey-id]');
|
|
254
|
+
const passkeyId = target?.getAttribute('data-passkey-id');
|
|
255
|
+
|
|
256
|
+
if (passkeyId) {
|
|
257
|
+
this.dispatchEvent(
|
|
258
|
+
new CustomEvent('remove-passkey-clicked', {
|
|
259
|
+
bubbles: true,
|
|
260
|
+
detail: { id: passkeyId, action: 'remove-passkey' },
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
get label() {
|
|
267
|
+
return this.getAttribute('label') || 'Passkey';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
get readOnly() {
|
|
271
|
+
return this.getAttribute('readonly') === 'true';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get allowRemove() {
|
|
275
|
+
return this.getAttribute('allow-remove') === 'true';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get format() {
|
|
279
|
+
return this.getAttribute('format')?.toUpperCase() || 'MM-DD-YYYY';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get addPasskeyButtonLabel() {
|
|
283
|
+
return this.getAttribute('add-passkey-button-label') || 'Add';
|
|
284
|
+
}
|
|
285
|
+
get removePasskeyButtonLabel() {
|
|
286
|
+
return this.getAttribute('remove-passkey-button-label') || 'Remove';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
get createdAtLabel() {
|
|
290
|
+
return this.getAttribute('created-at-label') || 'Date added:';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
get direction() {
|
|
294
|
+
return this.getAttribute('st-host-direction');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
onLabelChange() {
|
|
298
|
+
this.methodLabel.innerText = this.label;
|
|
299
|
+
this.methodLabel.setAttribute('title', this.label);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
updateButtonLabel(btnRef, label) {
|
|
303
|
+
let textSpanEle = btnRef.querySelector('span');
|
|
304
|
+
|
|
305
|
+
if (label) {
|
|
306
|
+
if (!textSpanEle) {
|
|
307
|
+
textSpanEle = document.createElement('span');
|
|
308
|
+
btnRef.appendChild(textSpanEle);
|
|
309
|
+
}
|
|
310
|
+
textSpanEle.textContent = label;
|
|
311
|
+
} else if (textSpanEle) {
|
|
312
|
+
btnRef.removeChild(textSpanEle);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
317
|
+
super.attributeChangedCallback?.(name, oldValue, newValue);
|
|
318
|
+
if (oldValue === newValue) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (name === 'label') {
|
|
323
|
+
this.onLabelChange();
|
|
324
|
+
} else if (name === 'add-passkey-button-label') {
|
|
325
|
+
this.updateButtonLabel(this.addPasskeyButton, this.addPasskeyButtonLabel);
|
|
326
|
+
} else if (name === 'empty') {
|
|
327
|
+
this.appsList.classList.toggle('hidden', newValue === 'true');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const { host } = {
|
|
333
|
+
host: { selector: () => ':host' },
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export const UserPasskeysClass = compose(
|
|
337
|
+
createStyleMixin({
|
|
338
|
+
mappings: {
|
|
339
|
+
hostWidth: { ...host, property: 'width' },
|
|
340
|
+
hostMinWidth: { ...host, property: 'min-width' },
|
|
341
|
+
hostDirection: [
|
|
342
|
+
{ ...host, property: 'direction' },
|
|
343
|
+
{
|
|
344
|
+
selector: () => 'descope-list-item',
|
|
345
|
+
property: 'direction',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
selector: () => 'descope-text',
|
|
349
|
+
property: 'direction',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
selector: () => 'descope-link',
|
|
353
|
+
property: LinkClass.cssVarList.hostDirection,
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
|
|
357
|
+
headerGap: {
|
|
358
|
+
selector: () => '.method',
|
|
359
|
+
property: 'gap',
|
|
360
|
+
},
|
|
361
|
+
listGap: {
|
|
362
|
+
selector: () => 'descope-list',
|
|
363
|
+
property: 'margin-top',
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
listItemsGap: {
|
|
367
|
+
property: ListClass.cssVarList.gap,
|
|
368
|
+
},
|
|
369
|
+
listBackgroundColor: {
|
|
370
|
+
selector: () => ListClass.componentName,
|
|
371
|
+
property: ListClass.cssVarList.backgroundColor,
|
|
372
|
+
},
|
|
373
|
+
listBorderRadius: {
|
|
374
|
+
selector: () => ListClass.componentName,
|
|
375
|
+
property: ListClass.cssVarList.borderRadius,
|
|
376
|
+
},
|
|
377
|
+
listBorderWidth: {
|
|
378
|
+
selector: () => ListClass.componentName,
|
|
379
|
+
property: ListClass.cssVarList.borderWidth,
|
|
380
|
+
},
|
|
381
|
+
listBoxShadow: {
|
|
382
|
+
selector: () => ListClass.componentName,
|
|
383
|
+
property: ListClass.cssVarList.boxShadow,
|
|
384
|
+
},
|
|
385
|
+
listPadding: [
|
|
386
|
+
{
|
|
387
|
+
selector: () => ListClass.componentName,
|
|
388
|
+
property: ListClass.cssVarList.verticalPadding,
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
selector: () => ListClass.componentName,
|
|
392
|
+
property: ListClass.cssVarList.horizontalPadding,
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
|
|
396
|
+
itemVerticalPadding: {
|
|
397
|
+
selector: ListItemClass.componentName,
|
|
398
|
+
property: ListItemClass.cssVarList.verticalPadding,
|
|
399
|
+
},
|
|
400
|
+
itemHorizontalPadding: {
|
|
401
|
+
selector: ListItemClass.componentName,
|
|
402
|
+
property: ListItemClass.cssVarList.horizontalPadding,
|
|
403
|
+
},
|
|
404
|
+
itemCursor: {
|
|
405
|
+
selector: ListItemClass.componentName,
|
|
406
|
+
property: ListItemClass.cssVarList.cursor,
|
|
407
|
+
},
|
|
408
|
+
itemOutline: {
|
|
409
|
+
selector: ListItemClass.componentName,
|
|
410
|
+
property: ListItemClass.cssVarList.outline,
|
|
411
|
+
},
|
|
412
|
+
itemBorderColor: {
|
|
413
|
+
selector: ListItemClass.componentName,
|
|
414
|
+
property: ListItemClass.cssVarList.borderColor,
|
|
415
|
+
},
|
|
416
|
+
itemBorderRadius: {
|
|
417
|
+
selector: ListItemClass.componentName,
|
|
418
|
+
property: ListItemClass.cssVarList.borderRadius,
|
|
419
|
+
},
|
|
420
|
+
itemBackgroundColor: {
|
|
421
|
+
selector: ListItemClass.componentName,
|
|
422
|
+
property: ListItemClass.cssVarList.backgroundColor,
|
|
423
|
+
},
|
|
424
|
+
itemContentGap: {
|
|
425
|
+
selector: () => '.content',
|
|
426
|
+
property: 'gap',
|
|
427
|
+
},
|
|
428
|
+
itemPanelGap: {
|
|
429
|
+
selector: () => '.main',
|
|
430
|
+
property: 'gap',
|
|
431
|
+
},
|
|
432
|
+
itemIconGap: {
|
|
433
|
+
selector: () => '.item',
|
|
434
|
+
property: 'gap',
|
|
435
|
+
},
|
|
436
|
+
itemIconSize: [
|
|
437
|
+
{
|
|
438
|
+
selector: () => '.item-icon',
|
|
439
|
+
property: 'width',
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
selector: () => '.item-icon',
|
|
443
|
+
property: 'height',
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
|
|
447
|
+
addButtonGap: {
|
|
448
|
+
selector: () => '.button',
|
|
449
|
+
property: 'margin-inline-end',
|
|
450
|
+
},
|
|
451
|
+
dateGap: {
|
|
452
|
+
selector: ' .meta',
|
|
453
|
+
property: 'gap',
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
removeButtonGap: {
|
|
457
|
+
selector: () =>
|
|
458
|
+
`${LinkClass.componentName}.remove-button > *:not(:last-child)`,
|
|
459
|
+
property: 'margin-inline-end',
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
draggableMixin,
|
|
464
|
+
createDynamicDataMixin({
|
|
465
|
+
itemRenderer,
|
|
466
|
+
sortFn,
|
|
467
|
+
rerenderAttrsList: ['created-at-label', 'format', 'allow-remove'],
|
|
468
|
+
}),
|
|
469
|
+
componentNameValidationMixin,
|
|
470
|
+
)(RawUserPasskeysClass);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import onePasswordDark from './providers/1password-dark.svg';
|
|
2
|
+
import onePasswordLight from './providers/1password-light.svg';
|
|
3
|
+
import appleDark from './providers/apple-dark.svg';
|
|
4
|
+
import appleLight from './providers/apple-light.svg';
|
|
5
|
+
import bitwardenDark from './providers/bitwarden-dark.svg';
|
|
6
|
+
import bitwardenLight from './providers/bitwarden-light.svg';
|
|
7
|
+
import chromeDark from './providers/chrome-dark.svg';
|
|
8
|
+
import chromeLight from './providers/chrome-light.svg';
|
|
9
|
+
import dashlaneDark from './providers/dashlane-dark.svg';
|
|
10
|
+
import dashlaneLight from './providers/dashlane-light.svg';
|
|
11
|
+
import edgeDark from './providers/edge-dark.svg';
|
|
12
|
+
import edgeLight from './providers/edge-light.svg';
|
|
13
|
+
import enpassDark from './providers/enpass-dark.svg';
|
|
14
|
+
import enpassLight from './providers/enpass-light.svg';
|
|
15
|
+
import googleDark from './providers/google-dark.svg';
|
|
16
|
+
import googleLight from './providers/google-light.svg';
|
|
17
|
+
import keepassxcDark from './providers/keepassxc-dark.svg';
|
|
18
|
+
import keepassxcLight from './providers/keepassxc-light.svg';
|
|
19
|
+
import keeperDark from './providers/keeper-dark.svg';
|
|
20
|
+
import keeperLight from './providers/keeper-light.svg';
|
|
21
|
+
import keyDark from './providers/key-dark.svg';
|
|
22
|
+
import keyLight from './providers/key-light.svg';
|
|
23
|
+
import lastPassDark from './providers/lastpass-dark.svg';
|
|
24
|
+
import lastPassLight from './providers/lastpass-light.svg';
|
|
25
|
+
import nordPassDark from './providers/nordpass-dark.svg';
|
|
26
|
+
import nordPassLight from './providers/nordpass-light.svg';
|
|
27
|
+
import phoneDark from './providers/phone-dark.svg';
|
|
28
|
+
import phoneLight from './providers/phone-light.svg';
|
|
29
|
+
import protonPassDark from './providers/protonpass-dark.svg';
|
|
30
|
+
import protonPassLight from './providers/protonpass-light.svg';
|
|
31
|
+
import unknownDark from './providers/unknown-device-dark.svg';
|
|
32
|
+
import unknownLight from './providers/unknown-device-light.svg';
|
|
33
|
+
import windowsDark from './providers/windows-dark.svg';
|
|
34
|
+
import windowsLight from './providers/windows-light.svg';
|
|
35
|
+
|
|
36
|
+
const ensureDate = (loginDate) => {
|
|
37
|
+
const numVal = parseInt(loginDate, 10);
|
|
38
|
+
if (Number.isNaN(numVal)) return 0;
|
|
39
|
+
return numVal;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const sortFn = (a, b) =>
|
|
43
|
+
ensureDate(b.createdAt) - ensureDate(a.createdAt);
|
|
44
|
+
|
|
45
|
+
export const parseDate = (epoch, format) => {
|
|
46
|
+
if (Number.isNaN(parseInt(epoch, 10))) return '';
|
|
47
|
+
|
|
48
|
+
const date = new Date(epoch);
|
|
49
|
+
const year = date.getFullYear();
|
|
50
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
51
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
52
|
+
const time = date.toLocaleTimeString('en-US', {
|
|
53
|
+
hour12: false,
|
|
54
|
+
hour: '2-digit',
|
|
55
|
+
minute: '2-digit',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const formatMap = {
|
|
59
|
+
'DD-MM-YYYY': `${day}/${month}/${year}`,
|
|
60
|
+
'YYYY-MM-DD': `${year}/${month}/${day}`,
|
|
61
|
+
'MM-DD-YYYY': `${month}/${day}/${year}`,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const dateStr = formatMap[format] || formatMap['MM-DD-YYYY'];
|
|
65
|
+
return `${dateStr} ${time}`;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const iconMap = {
|
|
69
|
+
apple: {
|
|
70
|
+
light: appleLight,
|
|
71
|
+
dark: appleDark,
|
|
72
|
+
},
|
|
73
|
+
google: {
|
|
74
|
+
light: googleLight,
|
|
75
|
+
dark: googleDark,
|
|
76
|
+
},
|
|
77
|
+
windows: {
|
|
78
|
+
light: windowsLight,
|
|
79
|
+
dark: windowsDark,
|
|
80
|
+
},
|
|
81
|
+
chrome: {
|
|
82
|
+
light: chromeLight,
|
|
83
|
+
dark: chromeDark,
|
|
84
|
+
},
|
|
85
|
+
edge: {
|
|
86
|
+
light: edgeLight,
|
|
87
|
+
dark: edgeDark,
|
|
88
|
+
},
|
|
89
|
+
samsung: {
|
|
90
|
+
light: unknownLight,
|
|
91
|
+
dark: unknownDark,
|
|
92
|
+
},
|
|
93
|
+
'1password': {
|
|
94
|
+
light: onePasswordLight,
|
|
95
|
+
dark: onePasswordDark,
|
|
96
|
+
},
|
|
97
|
+
bitwarden: {
|
|
98
|
+
light: bitwardenLight,
|
|
99
|
+
dark: bitwardenDark,
|
|
100
|
+
},
|
|
101
|
+
lastpass: {
|
|
102
|
+
light: lastPassLight,
|
|
103
|
+
dark: lastPassDark,
|
|
104
|
+
},
|
|
105
|
+
dashlane: {
|
|
106
|
+
light: dashlaneLight,
|
|
107
|
+
dark: dashlaneDark,
|
|
108
|
+
},
|
|
109
|
+
keeper: {
|
|
110
|
+
light: keeperLight,
|
|
111
|
+
dark: keeperDark,
|
|
112
|
+
},
|
|
113
|
+
keepassxc: {
|
|
114
|
+
light: keepassxcLight,
|
|
115
|
+
dark: keepassxcDark,
|
|
116
|
+
},
|
|
117
|
+
proton: {
|
|
118
|
+
light: protonPassLight,
|
|
119
|
+
dark: protonPassDark,
|
|
120
|
+
},
|
|
121
|
+
nordpass: {
|
|
122
|
+
light: nordPassLight,
|
|
123
|
+
dark: nordPassDark,
|
|
124
|
+
},
|
|
125
|
+
enpass: {
|
|
126
|
+
light: enpassLight,
|
|
127
|
+
dark: enpassDark,
|
|
128
|
+
},
|
|
129
|
+
key: {
|
|
130
|
+
light: keyLight,
|
|
131
|
+
dark: keyDark,
|
|
132
|
+
},
|
|
133
|
+
phone: {
|
|
134
|
+
light: phoneLight,
|
|
135
|
+
dark: phoneDark,
|
|
136
|
+
},
|
|
137
|
+
other: {
|
|
138
|
+
light: unknownLight,
|
|
139
|
+
dark: unknownDark,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const cloneSlottedNodes = (element, slotName) => {
|
|
144
|
+
if (!element || !slotName) return null;
|
|
145
|
+
const sources = element.querySelectorAll(`:scope > [slot="${slotName}"]`);
|
|
146
|
+
if (!sources.length) return null;
|
|
147
|
+
const fragment = document.createDocumentFragment();
|
|
148
|
+
sources.forEach((source) => {
|
|
149
|
+
const clone = source.content
|
|
150
|
+
? source.content.cloneNode(true)
|
|
151
|
+
: source.cloneNode(true);
|
|
152
|
+
if (clone.removeAttribute) clone.removeAttribute('slot');
|
|
153
|
+
fragment.append(clone);
|
|
154
|
+
});
|
|
155
|
+
return fragment;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const populateRemoveButton = (template, id, ref) => {
|
|
159
|
+
const button = template.content.querySelector('.remove-button');
|
|
160
|
+
button.dataset.passkeyId = id;
|
|
161
|
+
const clonedSlottedContent = cloneSlottedNodes(ref, 'remove-button');
|
|
162
|
+
if (clonedSlottedContent) button.append(clonedSlottedContent);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const getIcon = (deviceType) => {
|
|
166
|
+
const icon = iconMap[deviceType] || iconMap.other;
|
|
167
|
+
return {
|
|
168
|
+
iconSrc: icon.light,
|
|
169
|
+
iconSrcDark: icon.dark,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M4 6H22V4H4C2.9 4 2 4.9 2 6V17H0V20H14V17H4V6ZM23 8H17C16.45 8 16 8.45 16 9V19C16 19.55 16.45 20 17 20H23C23.55 20 24 19.55 24 19V9C24 8.45 23.55 8 23 8ZM22 17H18V10H22V17Z" fill="white"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M4 6.5H22V4.5H4C2.9 4.5 2 5.4 2 6.5V17.5H0V20.5H14V17.5H4V6.5ZM23 8.5H17C16.45 8.5 16 8.95 16 9.5V19.5C16 20.05 16.45 20.5 17 20.5H23C23.55 20.5 24 20.05 24 19.5V9.5C24 8.95 23.55 8.5 23 8.5ZM22 17.5H18V10.5H22V17.5Z" fill="#636C74"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '@descope-ui/descope-list';
|
|
2
|
+
import '@descope-ui/descope-list-item';
|
|
3
|
+
import '@descope-ui/descope-text';
|
|
4
|
+
import '@descope-ui/descope-link';
|
|
5
|
+
import '@descope-ui/descope-icon';
|
|
6
|
+
import '@descope-ui/descope-button';
|
|
7
|
+
|
|
8
|
+
import { componentName, UserPasskeysClass } from './UserPasskeysClass';
|
|
9
|
+
|
|
10
|
+
customElements.define(componentName, UserPasskeysClass);
|
|
11
|
+
|
|
12
|
+
export { UserPasskeysClass, componentName };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h24v24h-24z"/></clipPath><clipPath id="b"><path d="m1 1h22v22h-22z"/></clipPath><mask id="c" height="22" maskUnits="userSpaceOnUse" width="116" x="1" y="1"><path d="m1 1h115.077v22h-115.077z" fill="#fff"/></mask><g clip-path="url(#a)"><g clip-path="url(#b)"><g mask="url(#c)"><path d="m12 1c6.075 0 11 4.92495 11 11 0 6.0754-4.925 11-11 11-6.07505 0-11-4.9246-11-11 0-6.07505 4.92495-11 11-11zm-.8189 4.45552c-.4442 0-.6665.00038-.8363.08677-.1492.07608-.2709.19776-.34697.34706-.08631.16968-.08681.39216-.08681.83623v2.51781c0 .11093.00008.16686.01413.21815.01235.04535.03291.08817.06024.12641.03101.04324.07421.07878.16031.14876l.6371.51729c.1038.0843.1555.1269.1744.1777.0164.0444.0165.0935 0 .138-.019.0506-.0708.0926-.1744.1768l-.6371.5173c-.0861.0699-.1293.1055-.16031.1487-.02733.0383-.04789.0811-.06024.1264-.01405.0513-.01413.1072-.01413.2182l.00008 5.5173c0 .4444.00025.6674.08673.8371.07607.1491.19787.2702.34697.3462.1698.0864.3921.0868.8363.0868h1.6378c.4442 0 .6665-.0003.8363-.0868.1491-.076.2709-.1971.347-.3462.0864-.1697.0868-.3927.0868-.8371v-2.5178c0-.1109-.0001-.1669-.0141-.2181-.0124-.0454-.033-.0882-.0603-.1265-.031-.0432-.0742-.0787-.1603-.1487l-.6371-.5173c-.1037-.0842-.1563-.1261-.1752-.1768-.0165-.0445-.0165-.0943 0-.1389.0189-.0506.0715-.0925.1752-.1768l.6371-.5173c.0861-.0699.1293-.1055.1603-.1487.0273-.0383.0479-.0811.0603-.1264.014-.0513.0141-.1072.0141-.2182v-5.51732c0-.44406-.0005-.66655-.0868-.83623-.0761-.1493-.1978-.27098-.347-.34706-.1698-.08639-.3921-.08677-.8363-.08677z" fill="#fff"/></g></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h24v24h-24z"/></clipPath><clipPath id="b"><path d="m1 1h22v22h-22z"/></clipPath><mask id="c" height="22" maskUnits="userSpaceOnUse" width="116" x="1" y="1"><path d="m1 1h115.077v22h-115.077z" fill="#fff"/></mask><g clip-path="url(#a)"><g clip-path="url(#b)"><g mask="url(#c)"><path d="m12 1c6.075 0 11 4.92495 11 11 0 6.0754-4.925 11-11 11-6.07505 0-11-4.9246-11-11 0-6.07505 4.92495-11 11-11zm-.8189 4.45552c-.4442 0-.6665.00038-.8363.08677-.1492.07608-.2709.19776-.34697.34706-.08631.16968-.08681.39216-.08681.83623v2.51781c0 .11093.00008.16686.01413.21815.01235.04535.03291.08817.06024.12641.03101.04324.07421.07878.16031.14876l.6371.51729c.1038.0843.1555.1269.1744.1777.0164.0444.0165.0935 0 .138-.019.0506-.0708.0926-.1744.1768l-.6371.5173c-.0861.0699-.1293.1055-.16031.1487-.02733.0383-.04789.0811-.06024.1264-.01405.0513-.01413.1072-.01413.2182l.00008 5.5173c0 .4444.00025.6674.08673.8371.07607.1491.19787.2702.34697.3462.1698.0864.3921.0868.8363.0868h1.6378c.4442 0 .6665-.0003.8363-.0868.1491-.076.2709-.1971.347-.3462.0864-.1697.0868-.3927.0868-.8371v-2.5178c0-.1109-.0001-.1669-.0141-.2181-.0124-.0454-.033-.0882-.0603-.1265-.031-.0432-.0742-.0787-.1603-.1487l-.6371-.5173c-.1037-.0842-.1563-.1261-.1752-.1768-.0165-.0445-.0165-.0943 0-.1389.0189-.0506.0715-.0925.1752-.1768l.6371-.5173c.0861-.0699.1293-.1055.1603-.1487.0273-.0383.0479-.0811.0603-.1264.014-.0513.0141-.1072.0141-.2182v-5.51732c0-.44406-.0005-.66655-.0868-.83623-.0761-.1493-.1978-.27098-.347-.34706-.1698-.08639-.3921-.08677-.8363-.08677z" fill="#000"/></g></g></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12.2672 6.07692c.9984 0 2.25-.67133 2.9953-1.56643.675-.81119 1.1672-1.94406 1.1672-3.07692 0-.15385-.0141-.3077-.0422-.43357-1.1109.04196-2.4469.74126-3.2484 1.67832-.6328.71329-1.2094 1.83217-1.2094 2.97902 0 .16784.0281.33567.0422.39161.0703.01399.1828.02797.2953.02797zm-3.51564 16.92308c1.36404 0 1.96874-.9091 3.67034-.9091 1.7297 0 2.1094.8811 3.6281.8811 1.4906 0 2.4891-1.3706 3.4313-2.7133 1.0546-1.5384 1.4906-3.0489 1.5187-3.1188-.0984-.028-2.9531-1.1889-2.9531-4.4476 0-2.82517 2.25-4.09789 2.3765-4.1958-1.4906-2.12587-3.7546-2.18181-4.3734-2.18181-1.6734 0-3.0375 1.00699-3.8953 1.00699-.9281 0-2.1516-.95105-3.60001-.95105-2.75625 0-5.55469 2.26573-5.55469 6.54547 0 2.6573 1.04063 5.4685 2.32031 7.2867 1.09688 1.5385 2.05313 2.7972 3.43125 2.7972z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h24v24h-24z"/></clipPath><g clip-path="url(#a)"><path d="m12.7523 6.07692c.943 0 2.125-.67133 2.829-1.56643.6375-.81119 1.1023-1.94406 1.1023-3.07692 0-.15385-.0133-.3077-.0398-.43357-1.0493.04196-2.311.74126-3.068 1.67832-.5977.71329-1.1422 1.83217-1.1422 2.97902 0 .16784.0266.33567.0398.39161.0664.01399.1727.02797.2789.02797zm-3.32027 16.92308c1.28827 0 1.85937-.9091 3.46637-.9091 1.6336 0 1.9922.8811 3.4266.8811 1.4078 0 2.3508-1.3706 3.2406-2.7133.9961-1.5384 1.4078-3.0489 1.4344-3.1188-.093-.028-2.7891-1.1889-2.7891-4.4476 0-2.82517 2.125-4.09789 2.2446-4.1958-1.4078-2.12587-3.5461-2.18181-4.1305-2.18181-1.5805 0-2.8687 1.00699-3.6789 1.00699-.8766 0-2.032-.95105-3.40001-.95105-2.60312 0-5.24609 2.26573-5.24609 6.54547 0 2.6573.98281 5.4685 2.19141 7.2867 1.03593 1.5385 1.93906 2.7972 3.24062 2.7972z" fill="#000"/></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18.3 12.9171v-9.1663h-6.3v16.2853c1.1158-.602 2.1143-1.2554 2.9954-1.9626 2.2026-1.7573 3.3046-3.4761 3.3046-5.1564zm2.7-11.00059v11.00059c0 .8216-.1565 1.6354-.4708 2.4415-.3143.8074-.7031 1.5224-1.1676 2.1488-.4646.6251-1.0174 1.2335-1.6599 1.8266-.6425.5917-1.2345 1.0834-1.7786 1.4749-.544.3915-1.1108.7611-1.7015 1.1103-.5908.3491-1.0098.5853-1.2585.7085-.2487.1246-.4481.2195-.597.2863-.1124.0578-.2348.086-.3661.086s-.2537-.0282-.3661-.086c-.1502-.0668-.3496-.1617-.597-.2863-.2487-.1245-.6678-.3607-1.25851-.7085-.59074-.3479-1.1575-.7188-1.70154-1.1103s-1.1373-.8832-1.77853-1.4749c-.6425-.5918-1.19538-1.2002-1.65989-1.8266-.46452-.6251-.8533-1.3414-1.16761-2.1488-.3143-.8074-.47082-1.6212-.47082-2.4415v-11.00059c0-.24774.08962-.46339.2676-.64438s.38878-.27213.6324-.27213h16.2c.2436 0 .4544.09114.6324.27213s.2676.39664.2676.64438z" fill="#fff"/></svg>
|