@descope/web-components-ui 3.1.5 → 3.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descope/web-components-ui",
3
- "version": "3.1.5",
3
+ "version": "3.1.7",
4
4
  "description": "",
5
5
  "main": "dist/cjs/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -51,10 +51,10 @@
51
51
  "webpack-cli": "^7.0.0",
52
52
  "webpack-dev-server": "^5.0.0",
53
53
  "webpack-subresource-integrity": "5.2.0-rc.1",
54
- "rollup-replace-plugin": "3.1.5",
55
- "test-drivers": "3.1.5",
56
- "webpack-extract-font-loader": "3.1.5",
57
- "webpack-replace-plugin": "3.1.5"
54
+ "rollup-replace-plugin": "3.1.7",
55
+ "test-drivers": "3.1.7",
56
+ "webpack-extract-font-loader": "3.1.7",
57
+ "webpack-replace-plugin": "3.1.7"
58
58
  },
59
59
  "dependencies": {
60
60
  "@vaadin/checkbox": "24.3.4",
@@ -78,32 +78,32 @@
78
78
  "libphonenumber-js": "^1.11.12",
79
79
  "lodash.debounce": "4.0.8",
80
80
  "lodash.merge": "4.6.2",
81
- "@descope-ui/descope-address-field": "3.1.5",
82
- "@descope-ui/descope-apps-list": "3.1.5",
83
- "@descope-ui/descope-autocomplete-field": "3.1.5",
84
- "@descope-ui/descope-avatar": "3.1.5",
85
- "@descope-ui/descope-country-subdivision-city-field": "3.1.5",
86
- "@descope-ui/descope-badge": "3.1.5",
87
- "@descope-ui/descope-button": "3.1.5",
88
- "@descope-ui/common": "3.1.5",
89
- "@descope-ui/descope-collapsible-container": "3.1.5",
90
- "@descope-ui/descope-combo-box": "3.1.5",
91
- "@descope-ui/descope-enriched-text": "3.1.5",
92
- "@descope-ui/descope-icon": "3.1.5",
93
- "@descope-ui/descope-image": "3.1.5",
94
- "@descope-ui/descope-link": "3.1.5",
95
- "@descope-ui/descope-list": "3.1.5",
96
- "@descope-ui/descope-outbound-app-button": "3.1.5",
97
- "@descope-ui/descope-outbound-apps": "3.1.5",
98
- "@descope-ui/descope-ponyhot": "3.1.5",
99
- "@descope-ui/descope-recovery-codes": "3.1.5",
100
- "@descope-ui/descope-text": "3.1.5",
101
- "@descope-ui/descope-timer": "3.1.5",
102
- "@descope-ui/descope-timer-button": "3.1.5",
103
- "@descope-ui/descope-password-strength": "3.1.5",
104
- "@descope-ui/descope-tooltip": "3.1.5",
105
- "@descope-ui/descope-trusted-devices": "3.1.5",
106
- "@descope-ui/descope-list-item": "3.1.5"
81
+ "@descope-ui/common": "3.1.7",
82
+ "@descope-ui/descope-address-field": "3.1.7",
83
+ "@descope-ui/descope-country-subdivision-city-field": "3.1.7",
84
+ "@descope-ui/descope-apps-list": "3.1.7",
85
+ "@descope-ui/descope-autocomplete-field": "3.1.7",
86
+ "@descope-ui/descope-avatar": "3.1.7",
87
+ "@descope-ui/descope-badge": "3.1.7",
88
+ "@descope-ui/descope-button": "3.1.7",
89
+ "@descope-ui/descope-combo-box": "3.1.7",
90
+ "@descope-ui/descope-enriched-text": "3.1.7",
91
+ "@descope-ui/descope-collapsible-container": "3.1.7",
92
+ "@descope-ui/descope-icon": "3.1.7",
93
+ "@descope-ui/descope-image": "3.1.7",
94
+ "@descope-ui/descope-list": "3.1.7",
95
+ "@descope-ui/descope-list-item": "3.1.7",
96
+ "@descope-ui/descope-outbound-app-button": "3.1.7",
97
+ "@descope-ui/descope-link": "3.1.7",
98
+ "@descope-ui/descope-outbound-apps": "3.1.7",
99
+ "@descope-ui/descope-password-strength": "3.1.7",
100
+ "@descope-ui/descope-ponyhot": "3.1.7",
101
+ "@descope-ui/descope-recovery-codes": "3.1.7",
102
+ "@descope-ui/descope-text": "3.1.7",
103
+ "@descope-ui/descope-timer": "3.1.7",
104
+ "@descope-ui/descope-trusted-devices": "3.1.7",
105
+ "@descope-ui/descope-timer-button": "3.1.7",
106
+ "@descope-ui/descope-tooltip": "3.1.7"
107
107
  },
108
108
  "overrides": {
109
109
  "@vaadin/avatar": "24.3.4",
@@ -5,19 +5,23 @@ import { compose } from '../../helpers';
5
5
  import { getComponentName, observeChildren } from '../../helpers/componentHelpers';
6
6
  import { createBaseClass } from '../../baseClasses/createBaseClass';
7
7
  import { decode, tpl } from './helpers';
8
+ import copyIconSrc from './copy-icon.svg';
9
+ import checkIconSrc from './check-icon.svg';
8
10
 
9
11
  export const componentName = getComponentName('code-snippet');
10
12
 
11
- class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host > code' }) {
13
+ class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host > .wrapper' }) {
12
14
  static get observedAttributes() {
13
- return ['lang', 'inline'];
15
+ return ['lang', 'inline', 'copy-button'];
14
16
  }
15
17
 
16
18
  constructor() {
17
19
  super();
18
20
 
19
21
  this.attachShadow({ mode: 'open' }).innerHTML = `
20
- <code class="hljs"></code>
22
+ <div class="wrapper">
23
+ <code class="hljs"></code>
24
+ </div>
21
25
  `;
22
26
 
23
27
  injectStyle(
@@ -26,16 +30,52 @@ class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host
26
30
  display: inline-block;
27
31
  width: 100%;
28
32
  }
33
+ .wrapper {
34
+ display: grid;
35
+ width: 100%;
36
+ }
29
37
  code {
38
+ grid-area: 1 / 1;
30
39
  display: block;
31
40
  width: 100%;
32
41
  min-height: 1em;
33
- overflow-x: scroll;
34
- overflow-y: scroll;
42
+ overflow-x: auto;
35
43
  }
36
44
  pre {
37
45
  margin: 0;
38
46
  }
47
+ .copy-btn {
48
+ grid-area: 1 / 1;
49
+ place-self: start end;
50
+ margin: 8px;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ padding: 0;
55
+ border-style: solid;
56
+ cursor: pointer;
57
+ opacity: 0;
58
+ transition: opacity 150ms ease, background 150ms ease;
59
+ }
60
+ .wrapper:hover .copy-btn,
61
+ .copy-btn:focus-visible {
62
+ opacity: 1;
63
+ }
64
+ .copy-btn descope-icon {
65
+ width: 16px;
66
+ height: 16px;
67
+ pointer-events: none;
68
+ flex-shrink: 0;
69
+ }
70
+ .copy-btn .check-icon {
71
+ display: none;
72
+ }
73
+ .copy-btn.copied .check-icon {
74
+ display: block;
75
+ }
76
+ .copy-btn.copied descope-icon:not(.check-icon) {
77
+ display: none;
78
+ }
39
79
  `,
40
80
  this
41
81
  );
@@ -47,11 +87,15 @@ class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host
47
87
  this.lang = this.getAttribute('lang');
48
88
  this.isInline = this.getAttribute('inline') === 'true';
49
89
 
90
+ if (this.getAttribute('copy-button') === 'true') {
91
+ this.#initCopyButton();
92
+ }
93
+
50
94
  observeChildren(this, this.#renderSnippet.bind(this));
51
95
  }
52
96
 
53
97
  get contentNode() {
54
- return this.shadowRoot.querySelector(this.baseSelector);
98
+ return this.shadowRoot.querySelector('code');
55
99
  }
56
100
 
57
101
  attributeChangedCallback(attrName, oldValue, newValue) {
@@ -66,10 +110,58 @@ class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host
66
110
  this.lang = newValue;
67
111
  }
68
112
 
113
+ if (attrName === 'copy-button') {
114
+ if (newValue === 'true') {
115
+ this.#initCopyButton();
116
+ } else {
117
+ this.#destroyCopyButton();
118
+ }
119
+ }
120
+
69
121
  this.#renderSnippet();
70
122
  }
71
123
  }
72
124
 
125
+ // ── Copy button ────────────────────────────────────────────────────────────
126
+
127
+ #initCopyButton() {
128
+ if (this.shadowRoot.querySelector('.copy-btn')) return;
129
+
130
+ const btn = document.createElement('button');
131
+ btn.className = 'copy-btn';
132
+ btn.type = 'button';
133
+ btn.setAttribute('aria-label', 'Copy code');
134
+ const copyIcon = document.createElement('descope-icon');
135
+ copyIcon.setAttribute('src', copyIconSrc);
136
+
137
+ const checkIcon = document.createElement('descope-icon');
138
+ checkIcon.setAttribute('src', checkIconSrc);
139
+ checkIcon.classList.add('check-icon');
140
+
141
+ btn.appendChild(copyIcon);
142
+ btn.appendChild(checkIcon);
143
+ btn.addEventListener('click', () => this.#handleCopyClick());
144
+
145
+ this.shadowRoot.querySelector('.wrapper').appendChild(btn);
146
+ }
147
+
148
+ #destroyCopyButton() {
149
+ this.shadowRoot.querySelector('.copy-btn')?.remove();
150
+ }
151
+
152
+ #handleCopyClick() {
153
+ const btn = this.shadowRoot.querySelector('.copy-btn');
154
+ navigator.clipboard
155
+ .writeText(decode(this.textContent))
156
+ .then(() => {
157
+ btn.classList.add('copied');
158
+ setTimeout(() => btn.classList.remove('copied'), 2000);
159
+ })
160
+ .catch(() => {});
161
+ }
162
+
163
+ // ── Snippet rendering ──────────────────────────────────────────────────────
164
+
73
165
  #renderSnippet() {
74
166
  const sanitized = decode(this.textContent);
75
167
  const language = this.lang;
@@ -85,6 +177,8 @@ class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host
85
177
  }
86
178
  }
87
179
 
180
+ const copyBtn = { selector: () => '.copy-btn' };
181
+
88
182
  const {
89
183
  root,
90
184
  docTag,
@@ -236,6 +330,16 @@ export const CodeSnippetClass = compose(
236
330
  propertyTextColor: { ...property, property: 'color' },
237
331
  punctuationTextColor: { ...punctuation, property: 'color' },
238
332
  tagTextColor: { ...tag, property: 'color' },
333
+ copyButtonSize: [
334
+ { ...copyBtn, property: 'width' },
335
+ { ...copyBtn, property: 'height' },
336
+ ],
337
+ copyButtonBorderRadius: { ...copyBtn, property: 'border-radius' },
338
+ copyButtonBorderWidth: { ...copyBtn, property: 'border-width' },
339
+ copyButtonBorderColor: { ...copyBtn, property: 'border-color' },
340
+ copyButtonBgColor: { ...copyBtn, property: 'background-color' },
341
+ copyButtonHoverBgColor: { selector: () => '.copy-btn:hover', property: 'background-color' },
342
+ copyButtonColor: { ...copyBtn, property: 'color' },
239
343
  },
240
344
  }),
241
345
  draggableMixin,
@@ -0,0 +1,3 @@
1
+ <svg class="check-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="20 6 9 17 4 12"></polyline>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
3
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
4
+ </svg>
@@ -1,3 +1,4 @@
1
+ import '@descope-ui/descope-icon';
1
2
  import { componentName, CodeSnippetClass } from './CodeSnippetClass';
2
3
 
3
4
  customElements.define(componentName, CodeSnippetClass);
@@ -32,6 +32,18 @@ const GridMixin = (superclass) =>
32
32
  };
33
33
 
34
34
  this.baseElement.rowDetailsRenderer = this.#rowDetailsRenderer.bind(this);
35
+
36
+ // Stop wheel events from propagating to vaadin-grid when scrolling
37
+ // inside code snippets, so touchpad horizontal scroll works
38
+ this.baseElement.addEventListener(
39
+ 'wheel',
40
+ (e) => {
41
+ if (e.target.closest('descope-code-snippet')) {
42
+ e.stopPropagation();
43
+ }
44
+ },
45
+ true
46
+ );
35
47
  }
36
48
 
37
49
  // this renders the details panel content
@@ -342,6 +354,14 @@ export const GridClass = compose(
342
354
  grid-column: 1 / -1;
343
355
  order: 2;
344
356
  }
357
+ vaadin-grid details.row-details__item {
358
+ padding: 0;
359
+ }
360
+ vaadin-grid details.row-details__item > summary.row-details__label {
361
+ cursor: pointer;
362
+ list-style: revert;
363
+ display: revert;
364
+ }
345
365
  vaadin-grid .row-details__value.text {
346
366
  overflow: hidden;
347
367
  text-overflow: ellipsis;
@@ -349,8 +369,7 @@ export const GridClass = compose(
349
369
  }
350
370
  vaadin-grid .row-details__value.code {
351
371
  margin-top: 5px;
352
- max-height: 120px;
353
- overflow: scroll;
372
+ overflow-x: auto;
354
373
  font-size: 0.85em;
355
374
  }
356
375
  vaadin-grid vaadin-icon.toggle-details-button {
@@ -29,7 +29,7 @@ export const getValueType = (value) => {
29
29
  };
30
30
 
31
31
  export const renderCodeSnippet = (value, lang) =>
32
- `<descope-code-snippet lang="${lang}" class="row-details__value code">${escapeXML(value)}</descope-code-snippet>`;
32
+ `<descope-code-snippet copy-button="true" lang="${lang}" class="row-details__value code">${escapeXML(value)}</descope-code-snippet>`;
33
33
 
34
34
  export const renderText = (text) =>
35
35
  `<div class="row-details__value text" title="${text}">${escapeXML(text)}</div>`;
@@ -57,16 +57,31 @@ const defaultRowDetailsValueRenderer = (value) => {
57
57
  return renderText(value);
58
58
  };
59
59
 
60
+ const isCodeSnippetValue = (value) => {
61
+ const type = getValueType(value);
62
+ return (
63
+ type === 'object' ||
64
+ type === 'xml' ||
65
+ (type === 'array' && value.some((v) => getValueType(v) === 'object'))
66
+ );
67
+ };
68
+
60
69
  export const defaultRowDetailsRenderer = (item, itemLabelsMapping) => `
61
70
  <div class="row-details">
62
71
  ${Object.entries(item)
63
- .map(
64
- ([key, value]) =>
65
- `<div class="row-details__item" >
66
- <div class="row-details__label">${itemLabelsMapping[key] || toTitle(key)}</div>
72
+ .map(([key, value]) => {
73
+ const label = itemLabelsMapping[key] || toTitle(key);
74
+ if (isCodeSnippetValue(value)) {
75
+ return `<details class="row-details__item">
76
+ <summary class="row-details__label">${label}</summary>
77
+ ${defaultRowDetailsValueRenderer(value)}
78
+ </details>`;
79
+ }
80
+ return `<div class="row-details__item">
81
+ <div class="row-details__label">${label}</div>
67
82
  ${defaultRowDetailsValueRenderer(value)}
68
- </div>`
69
- )
83
+ </div>`;
84
+ })
70
85
  .join('\n')}
71
86
  </div>
72
87
  `;
@@ -39,6 +39,13 @@ const dark = {
39
39
  };
40
40
 
41
41
  const CodeSnippet = {
42
+ [vars.copyButtonSize]: '32px',
43
+ [vars.copyButtonBorderRadius]: globalRefs.radius.xs,
44
+ [vars.copyButtonBorderWidth]: globalRefs.border.xs,
45
+ [vars.copyButtonBorderColor]: globalRefs.colors.surface.light,
46
+ [vars.copyButtonBgColor]: globalRefs.colors.surface.highlight,
47
+ [vars.copyButtonHoverBgColor]: globalRefs.colors.surface.light,
48
+ [vars.copyButtonColor]: globalRefs.colors.surface.contrast,
42
49
  [vars.rootBgColor]: globalRefs.colors.surface.main,
43
50
  [vars.rootTextColor]: globalRefs.colors.surface.contrast,
44
51
  [vars.docTagTextColor]: light.color2,