@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/e2e/descope-user-passkeys.spec.ts +191 -0
  3. package/package.json +37 -0
  4. package/project.json +8 -0
  5. package/src/component/UserPasskeysClass.js +470 -0
  6. package/src/component/helpers.js +171 -0
  7. package/src/component/icons/checkmark.svg +3 -0
  8. package/src/component/icons/unknown-device-dark.svg +3 -0
  9. package/src/component/icons/unknown-device-light.svg +3 -0
  10. package/src/component/index.js +12 -0
  11. package/src/component/providers/1password-dark.svg +1 -0
  12. package/src/component/providers/1password-light.svg +1 -0
  13. package/src/component/providers/apple-dark.svg +1 -0
  14. package/src/component/providers/apple-light.svg +1 -0
  15. package/src/component/providers/bitwarden-dark.svg +1 -0
  16. package/src/component/providers/bitwarden-light.svg +1 -0
  17. package/src/component/providers/chrome-dark.svg +1 -0
  18. package/src/component/providers/chrome-light.svg +1 -0
  19. package/src/component/providers/dashlane-dark.svg +1 -0
  20. package/src/component/providers/dashlane-light.svg +1 -0
  21. package/src/component/providers/edge-dark.svg +46 -0
  22. package/src/component/providers/edge-light.svg +46 -0
  23. package/src/component/providers/enpass-dark.svg +10 -0
  24. package/src/component/providers/enpass-light.svg +10 -0
  25. package/src/component/providers/google-dark.svg +1 -0
  26. package/src/component/providers/google-light.svg +1 -0
  27. package/src/component/providers/keepassxc-dark.svg +1 -0
  28. package/src/component/providers/keepassxc-light.svg +1 -0
  29. package/src/component/providers/keeper-dark.svg +1 -0
  30. package/src/component/providers/keeper-light.svg +1 -0
  31. package/src/component/providers/key-dark.svg +1 -0
  32. package/src/component/providers/key-light.svg +1 -0
  33. package/src/component/providers/lastpass-dark.svg +1 -0
  34. package/src/component/providers/lastpass-light.svg +1 -0
  35. package/src/component/providers/nordpass-dark.svg +11 -0
  36. package/src/component/providers/nordpass-light.svg +11 -0
  37. package/src/component/providers/other-dark.svg +1 -0
  38. package/src/component/providers/other-light.svg +1 -0
  39. package/src/component/providers/phone-dark.svg +1 -0
  40. package/src/component/providers/phone-light.svg +1 -0
  41. package/src/component/providers/protonpass-dark.svg +1 -0
  42. package/src/component/providers/protonpass-light.svg +1 -0
  43. package/src/component/providers/unknown-dark.svg +1 -0
  44. package/src/component/providers/unknown-device-dark.svg +3 -0
  45. package/src/component/providers/unknown-device-light.svg +3 -0
  46. package/src/component/providers/unknown-light.svg +1 -0
  47. package/src/component/providers/windows-dark.svg +1 -0
  48. package/src/component/providers/windows-light.svg +1 -0
  49. package/src/theme.js +44 -0
  50. package/stories/descope-user-passkeys.stories.js +175 -0
  51. package/stories/icons/button-icon.svg +3 -0
  52. package/stories/icons/method-icon.svg +3 -0
  53. package/stories/icons/remove-icon.svg +3 -0
  54. 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="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8 0C3.6 0 0 3.6 0 8C0 12.4 3.6 16 8 16C12.4 16 16 12.4 16 8C16 3.6 12.4 0 8 0ZM7.1 11.7L2.9 7.6L4.3 6.2L7 8.9L12 4L13.4 5.4L7.1 11.7Z" fill="#4CAF50"/>
3
+ </svg>
@@ -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>