@descope/web-components-ui 3.16.3 → 3.17.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.
@@ -9,6 +9,8 @@ import { compose } from '../../helpers';
9
9
  import { getComponentName } from '../../helpers/componentHelpers';
10
10
  import deleteIcon from './delete.svg';
11
11
  import editIcon from './edit.svg';
12
+ import copyIcon from './copy.svg';
13
+ import checkIcon from './check.svg';
12
14
 
13
15
  export const componentName = getComponentName('user-attribute');
14
16
  class RawUserAttribute extends createBaseClass({
@@ -25,6 +27,10 @@ class RawUserAttribute extends createBaseClass({
25
27
  <descope-text st-text-align="auto" data-id="value-text" variant="body1" mode="primary" class="value"></descope-text>
26
28
  <div class="btn-wrapper">
27
29
  <descope-badge mode="default" bordered="true" size="xs"></descope-badge>
30
+ <descope-button size="sm" data-id="copy-btn" variant="link" mode="primary">
31
+ <slot name="copy-icon"></slot>
32
+ <descope-icon class="check-icon" src="${checkIcon}" src-dark="${checkIcon}"></descope-icon>
33
+ </descope-button>
28
34
  <descope-button size="sm" data-id="delete-btn" variant="link" mode="primary">
29
35
  <slot name="delete-icon"></slot>
30
36
  </descope-button>
@@ -71,12 +77,29 @@ class RawUserAttribute extends createBaseClass({
71
77
  }
72
78
 
73
79
  slot[name="edit-icon"],
74
- slot[name="delete-icon"] {
80
+ slot[name="delete-icon"],
81
+ slot[name="copy-icon"] {
75
82
  display: inline-flex;
76
83
  flex-shrink: 0;
77
84
  align-items: center;
78
85
  }
79
86
 
87
+ descope-button[data-id="copy-btn"].hidden {
88
+ display: none;
89
+ }
90
+
91
+ descope-button[data-id="copy-btn"] .check-icon {
92
+ display: none;
93
+ }
94
+
95
+ descope-button[data-id="copy-btn"].copied slot[name="copy-icon"] {
96
+ display: none;
97
+ }
98
+
99
+ descope-button[data-id="copy-btn"].copied .check-icon {
100
+ display: inline-flex;
101
+ }
102
+
80
103
  .content-wrapper {
81
104
  display: flex;
82
105
  flex-grow: 1;
@@ -119,7 +142,7 @@ class RawUserAttribute extends createBaseClass({
119
142
  color: var(${TextClass.cssVarList.textColor});
120
143
  }
121
144
 
122
- :host([readonly="true"]) descope-button {
145
+ :host([readonly="true"]) descope-button:not([data-id="copy-btn"]) {
123
146
  visibility: hidden;
124
147
  width: 0;
125
148
  }
@@ -127,6 +150,7 @@ class RawUserAttribute extends createBaseClass({
127
150
  this
128
151
  );
129
152
 
153
+ this.copyButton = this.shadowRoot.querySelector('descope-button[data-id="copy-btn"]');
130
154
  this.deleteButton = this.shadowRoot.querySelector('descope-button[data-id="delete-btn"]');
131
155
  this.editButton = this.shadowRoot.querySelector('descope-button[data-id="edit-btn"]');
132
156
  this.badge = this.shadowRoot.querySelector('descope-badge');
@@ -216,6 +240,14 @@ class RawUserAttribute extends createBaseClass({
216
240
  return this.getAttribute('hide-delete') === 'true';
217
241
  }
218
242
 
243
+ get isCopyEnabled() {
244
+ return this.getAttribute('show-copy') === 'true';
245
+ }
246
+
247
+ get copyButtonLabel() {
248
+ return this.getAttribute('copy-button-label') || '';
249
+ }
250
+
219
251
  init() {
220
252
  this.onLabelChange();
221
253
  this.onValueOrPlaceholderChange();
@@ -224,7 +256,9 @@ class RawUserAttribute extends createBaseClass({
224
256
  this.onBadgeTooltipTextChange();
225
257
  this.updateButtonLabel(this.editButton, this.editButtonLabel);
226
258
  this.updateButtonLabel(this.deleteButton, this.deleteButtonLabel);
259
+ this.updateButtonLabel(this.copyButton, this.copyButtonLabel);
227
260
  this.handleDeleteButtonVisibility();
261
+ this.handleCopyButtonState();
228
262
 
229
263
  this.deleteButton.addEventListener('click', () =>
230
264
  this.dispatchEvent(new CustomEvent('delete-clicked', { bubbles: true, composed: true }))
@@ -234,9 +268,35 @@ class RawUserAttribute extends createBaseClass({
234
268
  this.dispatchEvent(new CustomEvent('edit-clicked', { bubbles: true, composed: true }))
235
269
  );
236
270
 
271
+ this.copyButton.addEventListener('click', () => this.#handleCopyClick());
272
+
237
273
  this.setDefaultChildren();
238
274
  }
239
275
 
276
+ #handleCopyClick() {
277
+ // isDraggable is true only inside the page editor (craftjs sets draggable="true"
278
+ // for drag-and-drop); there the copy action must not run.
279
+ if (!this.value || this.isDraggable || !navigator.clipboard) return;
280
+
281
+ navigator.clipboard
282
+ .writeText(this.value)
283
+ .then(() => {
284
+ this.copyButton.classList.add('copied');
285
+ setTimeout(() => this.copyButton.classList.remove('copied'), 2000);
286
+ })
287
+ .catch(() => {});
288
+ }
289
+
290
+ handleCopyButtonState() {
291
+ this.copyButton.classList.toggle('hidden', !this.isCopyEnabled);
292
+
293
+ if (this.value) {
294
+ this.copyButton.removeAttribute('disabled');
295
+ } else {
296
+ this.copyButton.setAttribute('disabled', 'true');
297
+ }
298
+ }
299
+
240
300
  #addIcon(src, slotName) {
241
301
  const icon = document.createElement('descope-icon');
242
302
  icon.setAttribute('src', src);
@@ -246,9 +306,19 @@ class RawUserAttribute extends createBaseClass({
246
306
  }
247
307
 
248
308
  setDefaultChildren() {
249
- if (this.children.length) return;
250
- this.#addIcon(deleteIcon, 'delete-icon');
251
- this.#addIcon(editIcon, 'edit-icon');
309
+ if (!this.querySelector('[slot="delete-icon"]')) {
310
+ this.#addIcon(deleteIcon, 'delete-icon');
311
+ }
312
+ if (!this.querySelector('[slot="edit-icon"]')) {
313
+ this.#addIcon(editIcon, 'edit-icon');
314
+ }
315
+ this.setDefaultCopyIcon();
316
+ }
317
+
318
+ setDefaultCopyIcon() {
319
+ if (this.isCopyEnabled && !this.querySelector('[slot="copy-icon"]')) {
320
+ this.#addIcon(copyIcon, 'copy-icon');
321
+ }
252
322
  }
253
323
 
254
324
  static get observedAttributes() {
@@ -261,7 +331,9 @@ class RawUserAttribute extends createBaseClass({
261
331
  'badge-tooltip-text',
262
332
  'edit-button-label',
263
333
  'delete-button-label',
334
+ 'copy-button-label',
264
335
  'hide-delete',
336
+ 'show-copy',
265
337
  ].concat(super.observedAttributes);
266
338
  }
267
339
 
@@ -293,11 +365,19 @@ class RawUserAttribute extends createBaseClass({
293
365
  this.updateButtonLabel(this.editButton, this.editButtonLabel);
294
366
  } else if (name === 'delete-button-label') {
295
367
  this.updateButtonLabel(this.deleteButton, this.deleteButtonLabel);
368
+ } else if (name === 'copy-button-label') {
369
+ this.updateButtonLabel(this.copyButton, this.copyButtonLabel);
370
+ } else if (name === 'show-copy') {
371
+ this.setDefaultCopyIcon();
296
372
  }
297
373
 
298
374
  if (name === 'value' || name === 'required' || name === 'hide-delete') {
299
375
  this.handleDeleteButtonVisibility();
300
376
  }
377
+
378
+ if (name === 'value' || name === 'show-copy') {
379
+ this.handleCopyButtonState();
380
+ }
301
381
  }
302
382
  }
303
383
 
@@ -326,7 +406,12 @@ export const UserAttributeClass = compose(
326
406
  contentMinWidth: { ...contentWrapper, property: 'min-width' },
327
407
  badgeMaxWidth: { ...badge, property: 'max-width' },
328
408
  itemsGap: [{ property: 'gap' }, { ...contentWrapper, property: 'gap' }],
329
- iconColor: [{ selector: () => '::slotted(*)', property: IconClass.cssVarList.fill }],
409
+ iconColor: [
410
+ { selector: () => '::slotted(*)', property: IconClass.cssVarList.fill },
411
+ // the copied check-icon lives in the shadow DOM (not slotted), so it
412
+ // needs its own mapping to follow the same icon color
413
+ { selector: () => '.check-icon', property: IconClass.cssVarList.fill },
414
+ ],
330
415
  },
331
416
  }),
332
417
  draggableMixin,
@@ -0,0 +1,3 @@
1
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" fill="currentcolor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" fill="currentcolor"/>
3
+ </svg>
@@ -29,6 +29,8 @@ const Template = ({
29
29
  'edit-button-label': editButtonLabel,
30
30
  'delete-button-label': deleteButtonLabel,
31
31
  'hide-delete': hideDelete,
32
+ showCopy,
33
+ copyButtonLabel,
32
34
  customIcons,
33
35
  }) => `
34
36
  <descope-user-attribute
@@ -42,6 +44,8 @@ const Template = ({
42
44
  edit-button-label="${editButtonLabel || ''}"
43
45
  delete-button-label="${deleteButtonLabel || ''}"
44
46
  hide-delete="${hideDelete || 'false'}"
47
+ show-copy="${showCopy || 'false'}"
48
+ copy-button-label="${copyButtonLabel || ''}"
45
49
  st-host-direction="${direction ?? ''}"
46
50
  readonly="${readonly || false}"
47
51
  >
@@ -83,6 +87,14 @@ export default {
83
87
  name: 'Hide Delete',
84
88
  control: { type: 'boolean' },
85
89
  },
90
+ showCopy: {
91
+ name: 'Show Copy',
92
+ control: { type: 'boolean' },
93
+ },
94
+ copyButtonLabel: {
95
+ name: 'Copy Button Label',
96
+ control: { type: 'text' },
97
+ },
86
98
  customIcons: {
87
99
  name: 'Custom Icons',
88
100
  control: { type: 'boolean' },