@descope/web-components-ui 3.20.4 → 3.21.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.
Files changed (123) hide show
  1. package/dist/cjs/index.cjs.js +358 -25
  2. package/dist/cjs/index.cjs.js.map +1 -1
  3. package/dist/index.esm.js +358 -25
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/umd/1411.js +339 -0
  6. package/dist/umd/1411.js.map +1 -0
  7. package/dist/umd/189.js +171 -0
  8. package/dist/umd/189.js.map +1 -0
  9. package/dist/umd/2894.js +339 -0
  10. package/dist/umd/2894.js.map +1 -0
  11. package/dist/umd/3792.js +339 -0
  12. package/dist/umd/3792.js.map +1 -0
  13. package/dist/umd/{9117.js → 384.js} +121 -9
  14. package/dist/umd/{descope-tooltip.js.LICENSE.txt → 384.js.LICENSE.txt} +2 -2
  15. package/dist/umd/384.js.map +1 -0
  16. package/dist/umd/4268.js +339 -0
  17. package/dist/umd/4268.js.LICENSE.txt +11 -0
  18. package/dist/umd/4268.js.map +1 -0
  19. package/dist/umd/513.js +339 -0
  20. package/dist/umd/513.js.LICENSE.txt +11 -0
  21. package/dist/umd/513.js.map +1 -0
  22. package/dist/umd/6227.js +320 -0
  23. package/dist/umd/6227.js.map +1 -0
  24. package/dist/umd/6535.js +115 -0
  25. package/dist/umd/{9275.js.LICENSE.txt → 6535.js.LICENSE.txt} +0 -6
  26. package/dist/umd/6535.js.map +1 -0
  27. package/dist/umd/6649.js +339 -0
  28. package/dist/umd/6649.js.LICENSE.txt +11 -0
  29. package/dist/umd/6649.js.map +1 -0
  30. package/dist/umd/7125.js +339 -0
  31. package/dist/umd/7125.js.LICENSE.txt +11 -0
  32. package/dist/umd/7125.js.map +1 -0
  33. package/dist/umd/{5263.js → 8264.js} +5 -5
  34. package/dist/umd/8264.js.map +1 -0
  35. package/dist/umd/8983.js +7 -324
  36. package/dist/umd/8983.js.map +1 -1
  37. package/dist/umd/9030.js +339 -0
  38. package/dist/umd/9030.js.LICENSE.txt +11 -0
  39. package/dist/umd/9030.js.map +1 -0
  40. package/dist/umd/9380.js +130 -0
  41. package/dist/umd/9380.js.map +1 -0
  42. package/dist/umd/9506.js +339 -0
  43. package/dist/umd/9506.js.LICENSE.txt +11 -0
  44. package/dist/umd/9506.js.map +1 -0
  45. package/dist/umd/DescopeDev.js +1 -1
  46. package/dist/umd/DescopeDev.js.map +1 -1
  47. package/dist/umd/button-selection-group-fields-descope-button-multi-selection-group-index-js.js +1 -1
  48. package/dist/umd/button-selection-group-fields-descope-button-multi-selection-group-index-js.js.map +1 -1
  49. package/dist/umd/button-selection-group-fields-descope-button-selection-group-index-js.js +1 -1
  50. package/dist/umd/button-selection-group-fields-descope-button-selection-group-index-js.js.map +1 -1
  51. package/dist/umd/button-selection-group-fields-descope-button-selection-group-item-index-js.js +1 -338
  52. package/dist/umd/button-selection-group-fields-descope-button-selection-group-item-index-js.js.map +1 -1
  53. package/dist/umd/descope-apps-list.js +161 -1
  54. package/dist/umd/descope-apps-list.js.LICENSE.txt +17 -0
  55. package/dist/umd/descope-apps-list.js.map +1 -1
  56. package/dist/umd/descope-avatar.js +5 -20
  57. package/dist/umd/descope-avatar.js.LICENSE.txt +0 -12
  58. package/dist/umd/descope-avatar.js.map +1 -1
  59. package/dist/umd/descope-button.js +3 -3
  60. package/dist/umd/descope-button.js.LICENSE.txt +6 -0
  61. package/dist/umd/descope-button.js.map +1 -1
  62. package/dist/umd/descope-date-field-descope-calendar-index-js.js +1 -1
  63. package/dist/umd/descope-date-field-descope-calendar-index-js.js.map +1 -1
  64. package/dist/umd/descope-grid-descope-grid-custom-column-index-js.js +4 -321
  65. package/dist/umd/descope-grid-descope-grid-custom-column-index-js.js.LICENSE.txt +0 -6
  66. package/dist/umd/descope-grid-descope-grid-custom-column-index-js.js.map +1 -1
  67. package/dist/umd/descope-grid-descope-grid-item-details-column-index-js.js +5 -322
  68. package/dist/umd/descope-grid-descope-grid-item-details-column-index-js.js.map +1 -1
  69. package/dist/umd/descope-grid-descope-grid-text-column-index-js.js +4 -321
  70. package/dist/umd/descope-grid-descope-grid-text-column-index-js.js.LICENSE.txt +0 -6
  71. package/dist/umd/descope-grid-descope-grid-text-column-index-js.js.map +1 -1
  72. package/dist/umd/descope-multi-line-mappings.js +1 -1
  73. package/dist/umd/descope-multi-line-mappings.js.map +1 -1
  74. package/dist/umd/descope-multi-sso.js +1 -338
  75. package/dist/umd/descope-multi-sso.js.map +1 -1
  76. package/dist/umd/descope-outbound-app-button.js +1 -338
  77. package/dist/umd/descope-outbound-app-button.js.map +1 -1
  78. package/dist/umd/descope-recovery-codes.js +1 -1
  79. package/dist/umd/descope-recovery-codes.js.map +1 -1
  80. package/dist/umd/descope-timer-button.js +1 -338
  81. package/dist/umd/descope-timer-button.js.map +1 -1
  82. package/dist/umd/descope-tooltip.js +1 -2
  83. package/dist/umd/descope-tooltip.js.map +1 -1
  84. package/dist/umd/descope-upload-file-index-js.js +1 -338
  85. package/dist/umd/descope-upload-file-index-js.js.map +1 -1
  86. package/dist/umd/descope-user-attribute-index-js.js +1 -338
  87. package/dist/umd/descope-user-attribute-index-js.js.map +1 -1
  88. package/dist/umd/descope-user-auth-method-index-js.js +1 -338
  89. package/dist/umd/descope-user-auth-method-index-js.js.map +1 -1
  90. package/dist/umd/descope-user-passkeys.js +1 -338
  91. package/dist/umd/descope-user-passkeys.js.map +1 -1
  92. package/dist/umd/index.js +1 -1
  93. package/dist/umd/index.js.map +1 -1
  94. package/package.json +42 -42
  95. package/dist/umd/1453.js +0 -177
  96. package/dist/umd/1453.js.LICENSE.txt +0 -29
  97. package/dist/umd/1453.js.map +0 -1
  98. package/dist/umd/5263.js.map +0 -1
  99. package/dist/umd/6063.js +0 -18
  100. package/dist/umd/6063.js.map +0 -1
  101. package/dist/umd/6818.js +0 -3
  102. package/dist/umd/6818.js.map +0 -1
  103. package/dist/umd/8248.js +0 -171
  104. package/dist/umd/8248.js.map +0 -1
  105. package/dist/umd/8968.js +0 -432
  106. package/dist/umd/8968.js.map +0 -1
  107. package/dist/umd/9117.js.map +0 -1
  108. package/dist/umd/9275.js +0 -18
  109. package/dist/umd/9275.js.map +0 -1
  110. package/dist/umd/button-selection-group-fields-descope-button-selection-group-item-index-js.js.LICENSE.txt +0 -5
  111. package/dist/umd/descope-multi-sso.js.LICENSE.txt +0 -5
  112. package/dist/umd/descope-outbound-app-button.js.LICENSE.txt +0 -5
  113. package/dist/umd/descope-timer-button.js.LICENSE.txt +0 -5
  114. package/dist/umd/descope-upload-file-index-js.js.LICENSE.txt +0 -5
  115. package/dist/umd/descope-user-attribute-index-js.js.LICENSE.txt +0 -5
  116. package/dist/umd/descope-user-passkeys.js.LICENSE.txt +0 -5
  117. /package/dist/umd/{8968.js.LICENSE.txt → 1411.js.LICENSE.txt} +0 -0
  118. /package/dist/umd/{6063.js.LICENSE.txt → 189.js.LICENSE.txt} +0 -0
  119. /package/dist/umd/{9117.js.LICENSE.txt → 2894.js.LICENSE.txt} +0 -0
  120. /package/dist/umd/{descope-user-auth-method-index-js.js.LICENSE.txt → 3792.js.LICENSE.txt} +0 -0
  121. /package/dist/umd/{6818.js.LICENSE.txt → 6227.js.LICENSE.txt} +0 -0
  122. /package/dist/umd/{5263.js.LICENSE.txt → 8264.js.LICENSE.txt} +0 -0
  123. /package/dist/umd/{8248.js.LICENSE.txt → 9380.js.LICENSE.txt} +0 -0
@@ -9546,6 +9546,37 @@ var collapsibleContainer$1 = /*#__PURE__*/Object.freeze({
9546
9546
 
9547
9547
  const componentName$16 = getComponentName('recovery-codes');
9548
9548
 
9549
+ const NO_ACTION = 'none';
9550
+ const DEFAULT_ERROR_MESSAGE =
9551
+ 'Copy or download your recovery codes to continue.';
9552
+
9553
+ // default button label per action (buttons variant). the icons variant uses the vaadin
9554
+ // glyphs hardcoded in the shadow-DOM template below; the buttons variant uses image srcs.
9555
+ const ACTIONS = {
9556
+ copy: { defaultText: 'Copy' },
9557
+ download: { defaultText: 'Download' },
9558
+ };
9559
+
9560
+ // per-button config suffixes; the full attribute is `${action}-${suffix}`.
9561
+ // `icon`/`icon-dark` are image srcs (the seeded glyph or a custom upload), matching
9562
+ // the standard button's start-icon (light/dark), not a boolean.
9563
+ const BUTTON_SUFFIXES = [
9564
+ 'text',
9565
+ 'variant',
9566
+ 'color',
9567
+ 'size',
9568
+ 'icon',
9569
+ 'icon-dark',
9570
+ ];
9571
+
9572
+ // changing any of these rebuilds the buttons (so the editor preview stays live)
9573
+ const BUTTON_ATTRS = [
9574
+ 'show-download',
9575
+ ...Object.keys(ACTIONS).flatMap((action) =>
9576
+ BUTTON_SUFFIXES.map((suffix) => `${action}-${suffix}`),
9577
+ ),
9578
+ ];
9579
+
9549
9580
  const itemRenderer$5 = ({ value }, _, ref) => {
9550
9581
  return `
9551
9582
  <descope-text variant="${ref.variant}" mode="${ref.mode}">
@@ -9559,24 +9590,44 @@ class RawRecoveryCodes extends createBaseClass$1({
9559
9590
  baseSelector: ':host > div',
9560
9591
  }) {
9561
9592
  static get observedAttributes() {
9562
- return ['readonly'];
9593
+ return ['readonly', 'required', ...BUTTON_ATTRS];
9563
9594
  }
9564
9595
 
9596
+ // 'none' | 'copied' | 'downloaded' - the last action the user took on the codes
9597
+ #action = NO_ACTION;
9598
+
9599
+ // the error is shown only after a submit attempt (reportValidity), like inputs
9600
+ #showError = false;
9601
+
9602
+ // init() builds the buttons once with all seeded attrs; guards against rebuilding
9603
+ // on every per-attribute change fired during element upgrade (before init runs)
9604
+ #initialized = false;
9605
+
9565
9606
  constructor() {
9566
9607
  super();
9567
9608
 
9568
9609
  this.attachShadow({ mode: 'open' }).innerHTML = `
9569
9610
  <div class="wrapper">
9570
- <div class="list"></div>
9571
- <descope-text class="icon-wrapper">
9572
- <vaadin-icon class="icon" icon="vaadin:copy-o" title="Copy"></vaadin-icon>
9573
- </descope-text>
9611
+ <div class="codes-box">
9612
+ <div class="list"></div>
9613
+ <descope-text class="icon-wrapper">
9614
+ <div class="icons">
9615
+ <vaadin-icon class="icon copy-icon" icon="vaadin:copy-o" title="Copy"></vaadin-icon>
9616
+ <vaadin-icon class="icon download-icon" icon="vaadin:download-alt" title="Download"></vaadin-icon>
9617
+ </div>
9618
+ </descope-text>
9619
+ </div>
9620
+ <div class="error-message"></div>
9621
+ <div class="buttons-row"></div>
9574
9622
  </div>
9575
9623
  `;
9576
9624
 
9577
9625
  this.list = this.shadowRoot.querySelector('.list');
9578
- this.icon = this.shadowRoot.querySelector('.icon');
9626
+ this.copyIcon = this.shadowRoot.querySelector('.copy-icon');
9627
+ this.downloadIcon = this.shadowRoot.querySelector('.download-icon');
9579
9628
  this.iconWrapper = this.shadowRoot.querySelector('.icon-wrapper');
9629
+ this.errorMessageEle = this.shadowRoot.querySelector('.error-message');
9630
+ this.buttonsRow = this.shadowRoot.querySelector('.buttons-row');
9580
9631
 
9581
9632
  injectStyle(
9582
9633
  `
@@ -9585,8 +9636,14 @@ class RawRecoveryCodes extends createBaseClass$1({
9585
9636
  position: relative;
9586
9637
  }
9587
9638
  .wrapper {
9639
+ display: flex;
9640
+ flex-direction: column;
9641
+ }
9642
+ .codes-box {
9588
9643
  display: flex;
9589
9644
  justify-content: space-between;
9645
+ box-sizing: border-box;
9646
+ /* border shows the error ring inside the box; style/width/color/radius come from theme */
9590
9647
  }
9591
9648
  .list {
9592
9649
  display: flex;
@@ -9605,15 +9662,57 @@ class RawRecoveryCodes extends createBaseClass$1({
9605
9662
  .icon-wrapper {
9606
9663
  align-self: flex-start;
9607
9664
  }
9665
+ .icons {
9666
+ display: flex;
9667
+ flex-direction: column;
9668
+ }
9608
9669
  .icon {
9609
9670
  cursor: pointer;
9610
9671
  }
9611
9672
  .icon.copied {
9612
9673
  cursor: initial;
9674
+ /* block re-clicks while the checkmark shows, so rapid clicks cannot repeat the action */
9675
+ pointer-events: none;
9613
9676
  }
9614
- .icon.readonly {
9677
+ /* readonly is a host attribute, so disable pointer events from one place */
9678
+ :host([readonly="true"]) .icon,
9679
+ :host([readonly="true"]) .buttons-row {
9615
9680
  pointer-events: none;
9616
9681
  }
9682
+ .download-icon {
9683
+ display: none;
9684
+ }
9685
+ :host([show-download="true"]) .download-icon {
9686
+ display: inline-flex;
9687
+ }
9688
+ .error-message {
9689
+ display: none;
9690
+ /* width:0 + min-width:100% lets the message wrap to the component width without
9691
+ its text expanding the (shrink-to-fit) component; full-width still fills the parent */
9692
+ width: 0;
9693
+ min-width: 100%;
9694
+ box-sizing: border-box;
9695
+ line-height: 1.2;
9696
+ overflow-wrap: break-word;
9697
+ }
9698
+ :host([invalid]) .error-message {
9699
+ display: block;
9700
+ }
9701
+ .buttons-row {
9702
+ display: none;
9703
+ }
9704
+ :host([display-variant="buttons"]) .icon-wrapper {
9705
+ display: none;
9706
+ }
9707
+ :host([display-variant="buttons"]) .buttons-row {
9708
+ display: flex;
9709
+ flex-wrap: wrap;
9710
+ }
9711
+ /* grow to fill equally; don't shrink below content, so they wrap to a new line
9712
+ when they don't fit (like a container with auto-fit items) */
9713
+ .buttons-row > descope-button {
9714
+ flex: 1 0 auto;
9715
+ }
9617
9716
  `,
9618
9717
  this,
9619
9718
  );
@@ -9627,41 +9726,190 @@ class RawRecoveryCodes extends createBaseClass$1({
9627
9726
  return this.getAttribute('mode');
9628
9727
  }
9629
9728
 
9729
+ // the form value read by the flow SDK (#getFormData reads `.value`)
9730
+ get value() {
9731
+ return this.#action;
9732
+ }
9733
+
9734
+ // a boolean attribute is "on" when present and not explicitly "false"
9735
+ #boolAttr(name) {
9736
+ return this.hasAttribute(name) && this.getAttribute(name) !== 'false';
9737
+ }
9738
+
9739
+ get isRequired() {
9740
+ return this.#boolAttr('required');
9741
+ }
9742
+
9743
+ get isReadOnly() {
9744
+ return this.#boolAttr('readonly');
9745
+ }
9746
+
9747
+ get errorMessage() {
9748
+ return (
9749
+ this.getAttribute('data-errormessage-value-missing') ||
9750
+ DEFAULT_ERROR_MESSAGE
9751
+ );
9752
+ }
9753
+
9754
+ get codesText() {
9755
+ return String((this.data || []).map((rc) => rc.value).join('\n'));
9756
+ }
9757
+
9630
9758
  init() {
9631
9759
  super.init();
9632
9760
 
9633
- this.icon.addEventListener('click', this.onCopy.bind(this));
9761
+ this.copyIcon.addEventListener('click', this.onCopy.bind(this));
9762
+ this.downloadIcon.addEventListener('click', this.onDownload.bind(this));
9634
9763
 
9635
9764
  forwardAttrs(this, this.iconWrapper, { includeAttrs: ['variant', 'mode'] });
9765
+
9766
+ this.renderButtons();
9767
+ this.#initialized = true;
9636
9768
  }
9637
9769
 
9638
- onCopy() {
9639
- navigator.clipboard.writeText(
9640
- String(this.data.map((rc) => rc.value).join('\n')),
9770
+ get isDownloadEnabled() {
9771
+ return this.getAttribute('show-download') === 'true';
9772
+ }
9773
+
9774
+ // buttons variant: copy (+ download when enabled) as descope-buttons, each independently
9775
+ // configurable. rendered even in the icons variant (CSS hides the row) so a variant switch is instant.
9776
+ renderButtons() {
9777
+ if (!this.buttonsRow) return;
9778
+
9779
+ const buttons = [this.buildButton('copy')];
9780
+ if (this.isDownloadEnabled) {
9781
+ buttons.push(this.buildButton('download'));
9782
+ }
9783
+ this.buttonsRow.replaceChildren(...buttons);
9784
+ }
9785
+
9786
+ buildButton(action) {
9787
+ const { defaultText } = ACTIONS[action];
9788
+ const attr = (name, fallback) =>
9789
+ this.getAttribute(`${action}-${name}`) || fallback;
9790
+
9791
+ const button = document.createElement('descope-button');
9792
+ button.setAttribute('data-action', action);
9793
+ button.setAttribute('variant', attr('variant', 'outline'));
9794
+ button.setAttribute('mode', attr('color', 'primary'));
9795
+ button.setAttribute('size', attr('size', 'md'));
9796
+
9797
+ // icon is an image src - the seeded glyph or a custom upload (light/dark),
9798
+ // same as the standard button's start icon. rendered via descope-icon in the
9799
+ // default slot (not slot="prefix") so it centers with the label when the
9800
+ // button grows. descope-icon inlines SVGs, so the glyph (authored with
9801
+ // fill="currentColor") tracks the button text color while an uploaded image
9802
+ // keeps its own colors.
9803
+ const iconSrc = this.getAttribute(`${action}-icon`);
9804
+ if (iconSrc) {
9805
+ const icon = document.createElement('descope-icon');
9806
+ icon.setAttribute('src', iconSrc);
9807
+ const iconDark = this.getAttribute(`${action}-icon-dark`);
9808
+ if (iconDark) icon.setAttribute('src-dark', iconDark);
9809
+ button.appendChild(icon);
9810
+ }
9811
+
9812
+ button.appendChild(document.createTextNode(attr('text', defaultText)));
9813
+ button.addEventListener('click', () =>
9814
+ action === 'copy' ? this.onCopy() : this.onDownload(),
9641
9815
  );
9816
+ return button;
9817
+ }
9642
9818
 
9643
- this.icon.setAttribute('icon', 'vaadin:check-circle-o');
9644
- this.icon.setAttribute('title', 'Copied');
9645
- this.icon.classList.add('copied');
9819
+ onCopy() {
9820
+ if (this.isReadOnly) return;
9821
+ navigator.clipboard.writeText(this.codesText);
9822
+ this.showActionFeedback(this.copyIcon, 'Copied');
9823
+ this.recordAction('copied');
9824
+ }
9646
9825
 
9647
- setTimeout(() => {
9648
- this.icon.setAttribute('icon', 'vaadin:copy-o');
9649
- this.icon.setAttribute('title', 'Copy');
9650
- this.icon.classList.remove('copied');
9826
+ onDownload() {
9827
+ if (this.isReadOnly) return;
9828
+ const blob = new Blob([this.codesText], { type: 'text/plain' });
9829
+ const url = URL.createObjectURL(blob);
9830
+ const anchor = document.createElement('a');
9831
+ anchor.href = url;
9832
+ anchor.download = 'recovery-codes.txt';
9833
+ anchor.click();
9834
+ URL.revokeObjectURL(url);
9835
+
9836
+ this.showActionFeedback(this.downloadIcon, 'Downloaded');
9837
+ this.recordAction('downloaded');
9838
+ }
9839
+
9840
+ // copy/download share this - only the visual feedback differs by variant
9841
+ recordAction(action) {
9842
+ this.#action = action;
9843
+ // let the flow re-evaluate real-time conditions and pick up the new value
9844
+ this.dispatchEvent(new Event('change', { bubbles: true }));
9845
+ // acting clears the mandatory error
9846
+ if (this.checkValidity()) {
9847
+ this.#showError = false;
9848
+ }
9849
+ this.syncValidityUI();
9850
+ }
9851
+
9852
+ // icons variant: swap the icon to a checkmark for 5s (buttons variant is static)
9853
+ showActionFeedback(iconEle, doneTitle) {
9854
+ // on a rapid re-click the checkmark is already showing, so cancel the pending restore
9855
+ // and keep the glyph/title we saved the first time (never capture the checkmark itself)
9856
+ if (iconEle.feedbackTimer) {
9857
+ clearTimeout(iconEle.feedbackTimer);
9858
+ } else {
9859
+ iconEle.originalIcon = iconEle.getAttribute('icon');
9860
+ iconEle.originalTitle = iconEle.getAttribute('title');
9861
+ }
9862
+
9863
+ iconEle.setAttribute('icon', 'vaadin:check-circle-o');
9864
+ iconEle.setAttribute('title', doneTitle);
9865
+ iconEle.classList.add('copied');
9866
+
9867
+ iconEle.feedbackTimer = setTimeout(() => {
9868
+ iconEle.setAttribute('icon', iconEle.originalIcon);
9869
+ iconEle.setAttribute('title', iconEle.originalTitle);
9870
+ iconEle.classList.remove('copied');
9871
+ iconEle.feedbackTimer = null;
9651
9872
  }, 5000);
9652
9873
  }
9653
9874
 
9654
- onReadOnly(isReadOnly) {
9655
- this.icon.classList.toggle('readonly', isReadOnly);
9875
+ // the flow SDK's #validateInputs calls checkValidity/reportValidity on every *[name]
9876
+ checkValidity() {
9877
+ return this.isReadOnly || !this.isRequired || this.value !== NO_ACTION;
9878
+ }
9879
+
9880
+ reportValidity() {
9881
+ const valid = this.checkValidity();
9882
+ this.#showError = !valid;
9883
+ this.syncValidityUI();
9884
+ return valid;
9885
+ }
9886
+
9887
+ // single source of truth for the error UI - reflects `invalid` (theme `_invalid`
9888
+ // draws the outline) and fills the message. never toggled anywhere else.
9889
+ syncValidityUI() {
9890
+ this.toggleAttribute('invalid', this.#showError);
9891
+ if (this.errorMessageEle) {
9892
+ this.errorMessageEle.textContent = this.#showError
9893
+ ? this.errorMessage
9894
+ : '';
9895
+ }
9656
9896
  }
9657
9897
 
9658
9898
  attributeChangedCallback(attrName, oldValue, newValue) {
9659
9899
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
9660
9900
 
9661
- if (newValue !== oldValue) {
9662
- if (attrName === 'readonly') {
9663
- this.onReadOnly(newValue === 'true');
9664
- }
9901
+ if (newValue === oldValue) return;
9902
+
9903
+ // if it stops being required, drop any error already shown
9904
+ if (attrName === 'required' && !this.isRequired) {
9905
+ this.#showError = false;
9906
+ this.syncValidityUI();
9907
+ }
9908
+
9909
+ // only rebuild for live edits after init (init already built once with all attrs);
9910
+ // avoids rebuilding once per attribute during the initial element upgrade
9911
+ if (this.#initialized && BUTTON_ATTRS.includes(attrName)) {
9912
+ this.renderButtons();
9665
9913
  }
9666
9914
  }
9667
9915
  }
@@ -9681,10 +9929,66 @@ const RecoveryCodesClass = compose(
9681
9929
  textAlign: { selector: () => '.list', property: 'align-items' },
9682
9930
  iconColor: { selector: () => 'vaadin-icon', property: 'color' },
9683
9931
  iconSize: { selector: () => 'vaadin-icon', property: 'font-size' },
9684
- iconGap: { selector: () => '.wrapper', property: 'gap' },
9932
+ iconGap: { selector: () => '.codes-box', property: 'gap' },
9933
+ iconStackGap: { selector: () => '.icons', property: 'gap' },
9934
+ buttonsGap: { selector: () => '.buttons-row', property: 'gap' },
9935
+ sectionsGap: { selector: () => '.wrapper', property: 'gap' },
9685
9936
  bulletGap: { selector: () => 'descope-text span', property: 'gap' },
9937
+ // top + bottom padding sit inside the border (below the codes) in both variants; in the
9938
+ // buttons variant the bottom padding also repeats below the buttons (the buttons-row entry
9939
+ // is inert in the icons variant, where the row is display:none).
9940
+ verticalPadding: [
9941
+ { selector: () => '.codes-box', property: 'padding-top' },
9942
+ { selector: () => '.codes-box', property: 'padding-bottom' },
9943
+ { selector: () => '.buttons-row', property: 'padding-bottom' },
9944
+ ],
9945
+ // horizontal padding insets the codes (inside the border) and the buttons, but NOT the
9946
+ // error message, which stays aligned to the component edge (like the input error).
9947
+ horizontalPadding: [
9948
+ { selector: () => '.codes-box', property: 'padding-left' },
9949
+ { selector: () => '.codes-box', property: 'padding-right' },
9950
+ { selector: () => '.buttons-row', property: 'padding-left' },
9951
+ { selector: () => '.buttons-row', property: 'padding-right' },
9952
+ ],
9953
+ errorBorderColor: {
9954
+ selector: () => '.codes-box',
9955
+ property: 'border-color',
9956
+ },
9957
+ errorMessageColor: {
9958
+ selector: () => '.error-message',
9959
+ property: 'color',
9960
+ },
9961
+ errorMessageFontSize: {
9962
+ selector: () => '.error-message',
9963
+ property: 'font-size',
9964
+ },
9965
+ errorMessageFontWeight: {
9966
+ selector: () => '.error-message',
9967
+ property: 'font-weight',
9968
+ },
9969
+ errorMessageFontFamily: {
9970
+ selector: () => '.error-message',
9971
+ property: 'font-family',
9972
+ },
9973
+ boxBorderRadius: {
9974
+ selector: () => '.codes-box',
9975
+ property: 'border-radius',
9976
+ },
9977
+ errorBorderWidth: {
9978
+ selector: () => '.codes-box',
9979
+ property: 'border-width',
9980
+ },
9981
+ boxBorderStyle: {
9982
+ selector: () => '.codes-box',
9983
+ property: 'border-style',
9984
+ },
9686
9985
  },
9687
9986
  }),
9987
+ // also inject the shared input-wrapper theme onto our host (like the input components do),
9988
+ // so the error message + border track the console "customize inputs" tokens
9989
+ createStyleMixin$1({
9990
+ componentNameOverride: getComponentName('input-wrapper'),
9991
+ }),
9688
9992
  createDynamicDataMixin$1({
9689
9993
  itemRenderer: itemRenderer$5,
9690
9994
  rerenderAttrsList: ['variant', 'mode'],
@@ -9703,9 +10007,33 @@ const recoveryCodes = {
9703
10007
  [vars$T.hostDirection]: globalRefs$t.direction,
9704
10008
  [vars$T.iconColor]: useVar$1(textVars$6.textColor),
9705
10009
  [vars$T.iconSize]: useVar$1(textVars$6.fontSize),
10010
+ // gap between the codes and the icon column
9706
10011
  [vars$T.iconGap]: '8px',
10012
+ // gap between the stacked copy and download icons
10013
+ [vars$T.iconStackGap]: '16px',
10014
+ // gap between the copy and download buttons (buttons variant)
10015
+ [vars$T.buttonsGap]: '8px',
10016
+ // gap between the codes, the error message and the buttons
10017
+ [vars$T.sectionsGap]: '8px',
9707
10018
  [vars$T.bulletGap]: '0.35em',
9708
10019
 
10020
+ // no padding by default so the current look is unchanged; the editor overrides per instance
10021
+ [vars$T.verticalPadding]: '0',
10022
+ [vars$T.horizontalPadding]: '0',
10023
+
10024
+ // error ring is transparent until the component is marked invalid (see _invalid below).
10025
+ // message color/font + border come from the shared input-wrapper tokens, so customizing
10026
+ // inputs in the console updates the recovery-codes error styling from the same place.
10027
+ [vars$T.errorBorderColor]: 'transparent',
10028
+ [vars$T.errorBorderWidth]: refs$1.borderWidth,
10029
+ [vars$T.boxBorderRadius]: refs$1.borderRadius,
10030
+ // local default; can later map to the input-wrapper border style if it exposes one
10031
+ [vars$T.boxBorderStyle]: 'solid',
10032
+ [vars$T.errorMessageColor]: refs$1.errorMessageTextColor,
10033
+ [vars$T.errorMessageFontSize]: refs$1.errorMessageFontSize,
10034
+ [vars$T.errorMessageFontWeight]: refs$1.errorMessageFontWeight,
10035
+ [vars$T.errorMessageFontFamily]: refs$1.fontFamily,
10036
+
9709
10037
  textAlign: {
9710
10038
  right: { [vars$T.textAlign]: 'flex-end' },
9711
10039
  left: { [vars$T.textAlign]: 'flex-start' },
@@ -9715,6 +10043,11 @@ const recoveryCodes = {
9715
10043
  _fullWidth: {
9716
10044
  [vars$T.hostWidth]: '100%',
9717
10045
  },
10046
+
10047
+ // reflected `invalid` attribute (set by the component on a failed submit)
10048
+ _invalid: {
10049
+ [vars$T.errorBorderColor]: refs$1.errorMessageTextColor,
10050
+ },
9718
10051
  };
9719
10052
 
9720
10053
  var recoveryCodes$1 = /*#__PURE__*/Object.freeze({