@vonage/vivid 5.4.0 → 5.5.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 (161) hide show
  1. package/bundled/base-color-picker.cjs +18 -13
  2. package/bundled/base-color-picker.js +98 -81
  3. package/bundled/calendar-picker.template.cjs +1 -1
  4. package/bundled/calendar-picker.template.js +2 -2
  5. package/bundled/char-count.cjs +1 -1
  6. package/bundled/char-count.js +1 -1
  7. package/bundled/definition10.cjs +1 -1
  8. package/bundled/definition10.js +2 -2
  9. package/bundled/definition11.cjs +12 -19
  10. package/bundled/definition11.js +73 -204
  11. package/bundled/definition12.cjs +19 -10
  12. package/bundled/definition12.js +217 -36
  13. package/bundled/definition13.cjs +10 -1
  14. package/bundled/definition13.js +38 -14
  15. package/bundled/definition14.cjs +1 -5
  16. package/bundled/definition14.js +15 -24
  17. package/bundled/definition15.cjs +5 -30
  18. package/bundled/definition15.js +22 -73
  19. package/bundled/definition16.cjs +30 -19
  20. package/bundled/definition16.js +74 -97
  21. package/bundled/definition17.cjs +19 -13
  22. package/bundled/definition17.js +83 -117
  23. package/bundled/definition18.cjs +13 -12
  24. package/bundled/definition18.js +114 -71
  25. package/bundled/definition19.cjs +16 -16
  26. package/bundled/definition19.js +87 -84
  27. package/bundled/definition2.cjs +9 -9
  28. package/bundled/definition2.js +84 -129
  29. package/bundled/definition3.cjs +1 -1
  30. package/bundled/definition3.js +1 -1
  31. package/bundled/listbox.cjs +1 -1
  32. package/bundled/listbox.js +1 -1
  33. package/bundled/localized.cjs +1 -1
  34. package/bundled/localized.js +48 -35
  35. package/bundled/mixins.cjs +1 -1
  36. package/bundled/mixins.js +1 -1
  37. package/bundled/picker-field.template.cjs +14 -14
  38. package/bundled/picker-field.template.js +35 -56
  39. package/bundled/time-selection-picker.template.cjs +12 -12
  40. package/bundled/time-selection-picker.template.js +13 -12
  41. package/bundled/trapped-focus.cjs +1 -0
  42. package/bundled/trapped-focus.js +26 -0
  43. package/bundled/vivid-element.cjs +1 -1
  44. package/bundled/vivid-element.js +1 -1
  45. package/calendar/index.cjs +13 -13
  46. package/calendar/index.js +172 -144
  47. package/color-picker/definition.cjs +208 -112
  48. package/color-picker/definition.js +208 -112
  49. package/color-picker/index.cjs +104 -75
  50. package/color-picker/index.js +412 -326
  51. package/combobox/index.cjs +1 -1
  52. package/combobox/index.js +1 -1
  53. package/contextual-help/index.cjs +1 -1
  54. package/contextual-help/index.js +1 -1
  55. package/custom-elements.json +49 -0
  56. package/data-grid/index.cjs +1 -1
  57. package/data-grid/index.js +1 -1
  58. package/date-picker/index.cjs +1 -1
  59. package/date-picker/index.js +2 -2
  60. package/date-range-picker/index.cjs +1 -1
  61. package/date-range-picker/index.js +2 -2
  62. package/date-time-picker/index.cjs +1 -1
  63. package/date-time-picker/index.js +2 -2
  64. package/dial-pad/definition.cjs +139 -0
  65. package/dial-pad/definition.js +139 -0
  66. package/dial-pad/index.cjs +27 -20
  67. package/dial-pad/index.js +177 -100
  68. package/divider/index.cjs +1 -1
  69. package/divider/index.js +1 -1
  70. package/icon/definition.cjs +56 -22
  71. package/icon/definition.js +56 -22
  72. package/lib/color-picker/color-picker.d.ts +390 -12
  73. package/lib/color-picker/locale.d.ts +4 -0
  74. package/lib/date-picker/date-picker.d.ts +38 -38
  75. package/lib/date-range-picker/date-range-picker.d.ts +20 -20
  76. package/lib/date-time-picker/date-time-picker.d.ts +40 -40
  77. package/lib/dial-pad/dial-pad.d.ts +1 -0
  78. package/lib/icon/icon.d.ts +1 -0
  79. package/lib/simple-color-picker/simple-color-picker.d.ts +2 -1
  80. package/lib/time-picker/time-picker.d.ts +20 -20
  81. package/locales/de-DE.cjs +16 -3
  82. package/locales/de-DE.js +16 -3
  83. package/locales/en-GB.cjs +17 -4
  84. package/locales/en-GB.js +17 -4
  85. package/locales/en-US.cjs +17 -4
  86. package/locales/en-US.js +17 -4
  87. package/locales/ja-JP.cjs +16 -3
  88. package/locales/ja-JP.js +16 -3
  89. package/locales/zh-CN.cjs +15 -2
  90. package/locales/zh-CN.js +15 -2
  91. package/number-field/index.cjs +1 -1
  92. package/number-field/index.js +3 -3
  93. package/option/index.cjs +1 -1
  94. package/option/index.js +1 -1
  95. package/package.json +1 -1
  96. package/progress-ring/index.cjs +1 -1
  97. package/progress-ring/index.js +1 -1
  98. package/radio/index.cjs +1 -1
  99. package/radio/index.js +1 -1
  100. package/radio-group/index.cjs +1 -1
  101. package/radio-group/index.js +1 -1
  102. package/rich-text-editor/index.cjs +1 -1
  103. package/rich-text-editor/index.js +3 -3
  104. package/searchable-select/index.cjs +1 -1
  105. package/searchable-select/index.js +3 -3
  106. package/select/definition.cjs +6 -3
  107. package/select/definition.js +6 -3
  108. package/selectable-box/index.cjs +1 -1
  109. package/selectable-box/index.js +1 -1
  110. package/shared/color-picker/base-color-picker.d.ts +2 -1
  111. package/shared/picker-field/mixins/calendar-picker.d.ts +10 -10
  112. package/shared/picker-field/mixins/calendar-picker.template.d.ts +10 -10
  113. package/shared/picker-field/mixins/min-max-calendar-picker.d.ts +20 -20
  114. package/shared/picker-field/mixins/single-date-picker.d.ts +28 -28
  115. package/shared/picker-field/mixins/single-value-picker.d.ts +8 -8
  116. package/shared/picker-field/mixins/time-selection-picker.d.ts +20 -20
  117. package/shared/picker-field/mixins/time-selection-picker.template.d.ts +20 -20
  118. package/simple-color-picker/definition.cjs +8 -6
  119. package/simple-color-picker/definition.js +8 -6
  120. package/simple-color-picker/index.cjs +6 -6
  121. package/simple-color-picker/index.js +41 -39
  122. package/styles/core/all.css +1 -1
  123. package/styles/core/theme.css +1 -1
  124. package/styles/core/typography.css +1 -1
  125. package/styles/tokens/theme-dark.css +4 -4
  126. package/styles/tokens/theme-light.css +4 -4
  127. package/styles/tokens/vivid-2-compat.css +1 -1
  128. package/tag/definition.cjs +34 -14
  129. package/tag/definition.js +34 -14
  130. package/tag/index.cjs +25 -12
  131. package/tag/index.js +64 -47
  132. package/tag-group/definition.cjs +1 -2
  133. package/tag-group/definition.js +1 -2
  134. package/tag-group/index.cjs +1 -1
  135. package/tag-group/index.js +11 -12
  136. package/text-area/index.cjs +1 -1
  137. package/text-area/index.js +2 -2
  138. package/time-picker/index.cjs +1 -1
  139. package/time-picker/index.js +1 -1
  140. package/toggletip/index.cjs +1 -1
  141. package/toggletip/index.js +1 -1
  142. package/tooltip/definition.cjs +2 -2
  143. package/tooltip/definition.js +2 -2
  144. package/tooltip/index.cjs +1 -1
  145. package/tooltip/index.js +1 -1
  146. package/unbundled/base-color-picker.cjs +36 -18
  147. package/unbundled/base-color-picker.js +36 -18
  148. package/unbundled/calendar-picker.template.cjs +1 -1
  149. package/unbundled/calendar-picker.template.js +1 -1
  150. package/unbundled/picker-field.template.cjs +2 -35
  151. package/unbundled/picker-field.template.js +2 -34
  152. package/unbundled/time-selection-picker.template.cjs +2 -1
  153. package/unbundled/time-selection-picker.template.js +2 -1
  154. package/unbundled/trapped-focus.cjs +37 -0
  155. package/unbundled/trapped-focus.js +34 -0
  156. package/unbundled/vivid-element.cjs +1 -1
  157. package/unbundled/vivid-element.js +1 -1
  158. package/visually-hidden/index.cjs +1 -1
  159. package/visually-hidden/index.js +1 -1
  160. package/bundled/_has.cjs +0 -1
  161. package/bundled/_has.js +0 -34
@@ -4,15 +4,18 @@ import { VwcIconElement as Icon, iconDefinition } from '../icon/definition.js';
4
4
  import { a as WithFeedback, f as feedbackMessageDefinition } from '../unbundled/mixins.js';
5
5
  import { attr, observable, nullableNumberConverter, ref, when, html, slotted, repeat } from '@microsoft/fast-element';
6
6
  import { D as DelegatesAria, d as delegateAria } from '../unbundled/delegates-aria.js';
7
+ import { h as handleEscapeKeyAndStopPropogation } from '../unbundled/index.js';
7
8
  import { W as WithContextualHelp } from '../unbundled/with-contextual-help.js';
9
+ import { T as TrappedFocus } from '../unbundled/trapped-focus.js';
8
10
  import { B as BaseColorPicker, i as isValidHexColor } from '../unbundled/base-color-picker.js';
9
11
  import { W as WithErrorText } from '../unbundled/with-error-text.js';
10
12
  import { W as WithSuccessText } from '../unbundled/with-success-text.js';
11
13
  import { classNames } from '@microsoft/fast-web-utilities';
12
14
  import { VwcTextFieldElement as TextField, textFieldDefinition } from '../text-field/definition.js';
13
15
  import { B as Button, b as buttonDefinition } from '../unbundled/definition.js';
16
+ import { VwcTooltipElement as Tooltip, tooltipDefinition } from '../tooltip/definition.js';
14
17
 
15
- const styles = ".palette{display:grid;grid-template-columns:repeat(var(--swatches-per-row, 7),var(--_color-swatch-size, 24px))}.swatch{position:relative;padding:0;border:none;border-radius:4px;background-color:var(--swatch-color);block-size:var(--_color-swatch-size, 24px);color:var(--vvd-color-canvas);cursor:pointer;inline-size:var(--_color-swatch-size, 24px)}.swatch.contrast{color:var(--vvd-color-canvas-text)}.swatch:focus-visible{outline:none;box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px));--focus-stroke-gap-color: transparent;--focus-inset: -3px}:host{display:inline-block}.base{display:inline-block;min-inline-size:var(--_color-picker-min-inline-size, 280px)}.control{inline-size:100%}.button{display:inline-flex;box-sizing:border-box;align-items:center;border:0 none;border-radius:4px;margin:0;background-color:var(--button-color, var(--vvd-color-canvas-text));block-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2)));color:var(--vvd-color-canvas);inline-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2)));padding-inline:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2))/4);text-decoration:none;vertical-align:middle}.button.contrast{color:var(--vvd-color-canvas-text)}.button:not(.disabled){cursor:pointer}.button.disabled{background-color:var(--vvd-color-neutral-300);color:var(--vvd-color-neutral-500);cursor:not-allowed}.button:focus-visible{--focus-stroke-gap-color: transparent;box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px))}.button [data-vvd-component=icon]{font-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2))/2);line-height:1}.dialog{display:flex;flex-direction:column;min-inline-size:var(--_color-picker-popup-min-inline-size, 264px)}.dialog .header{display:inline-flex;align-items:center;justify-content:space-between;padding-block:8px;padding-inline:16px}.dialog .header-title{font:var(--vvd-typography-base-extended-bold)}.dialog .body{display:flex;flex-direction:column;gap:16px;margin-block-end:16px;padding-inline:16px}.dialog .hex-input-wrapper{display:flex;align-items:stretch;gap:8px}.dialog .hex-input-wrapper [data-vvd-component=button]{flex:0 0 auto}.dialog .footer{padding:16px;border-top:1px solid var(--vvd-color-neutral-200)}.dialog .footer-title{display:block;margin-block-end:8px}.dialog .palette{gap:var(--_color-swatches-gap, 8px)}vvd-hex-input{min-width:0;flex:1 1 auto}vvd-hex-input::part(input){width:100%;height:100%;box-sizing:border-box;border:0 none;border-radius:8px;background-color:var(--vvd-color-canvas);box-shadow:inset 0 0 0 1px var(--vvd-color-neutral-500);font:var(--vvd-typography-base);outline:transparent;padding-inline:calc(1px*(40 + 4*clamp(-1,var(--vvd-size-density, 0),2))*.4)}vvd-hex-input::part(input):focus-visible{box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px));--focus-stroke-gap-color: transparent}vvd-hex-picker{width:auto;height:auto;gap:16px;inline-size:100%}vvd-hex-picker::part(saturation){border-radius:8px;block-size:120px}vvd-hex-picker::part(hue){flex:0 0 8px;border-radius:8px}vvd-hex-picker::part(hue-pointer){width:10px;height:10px}vvd-hex-picker::part(saturation-pointer){width:18px;height:18px}::part(popup-base){inline-size:max-content;min-block-size:var(--_color-picker-popup-min-inline-size, 264px);min-inline-size:var(--_color-picker-popup-min-inline-size, 264px)}";
18
+ const styles = ".palette{display:grid;grid-template-columns:repeat(var(--swatches-per-row, 7),var(--_color-swatch-size, 24px))}.swatch{position:relative;padding:0;border-radius:4px;background-color:var(--swatch-color);block-size:var(--_color-swatch-size, 24px);color:var(--vvd-color-canvas);cursor:pointer;inline-size:var(--_color-swatch-size, 24px)}.swatch:not(.contrast){border:none}.swatch.contrast{border:1px solid var(--vvd-color-neutral-400);color:var(--vvd-color-canvas-text)}.swatch:focus-visible{outline:none;box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px));--focus-stroke-gap-color: transparent;--focus-inset: -3px}:host{display:inline-block;--_low-ink-color: var(--vvd-color-neutral-600)}.base{display:inline-block;min-inline-size:var(--_color-picker-min-inline-size, 280px)}.control{inline-size:100%}.button{display:inline-flex;box-sizing:border-box;align-items:center;border:0 none;border-radius:4px;margin:0;background-color:var(--button-color, var(--vvd-color-canvas-text));block-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2)));color:var(--vvd-color-canvas);inline-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2)));padding-inline:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2))/4);text-decoration:none;vertical-align:middle}.button.contrast{color:var(--vvd-color-canvas-text)}.button:not(.disabled){cursor:pointer}.button.disabled{background-color:var(--vvd-color-neutral-300);color:var(--vvd-color-neutral-500);cursor:not-allowed}.button:focus-visible{--focus-stroke-gap-color: transparent;box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px))}.button [data-vvd-component=icon]{font-size:calc(1px*(32 + 4*clamp(-1,var(--vvd-size-density, 0),2))/2);line-height:1}.dialog{display:flex;flex-direction:column;min-inline-size:var(--_color-picker-popup-min-inline-size, 264px)}.dialog .header{display:inline-flex;align-items:center;justify-content:space-between;padding-block:8px;padding-inline:16px}.dialog .header-title{font:var(--vvd-typography-base-extended-bold);margin-block:0}.dialog .body{display:flex;flex-direction:column;gap:16px;margin-block-end:16px;padding-inline:16px}.dialog .hex-input-wrapper{display:flex;align-items:stretch;gap:8px}.dialog .hex-input-wrapper [data-vvd-component=button]{flex:0 0 auto}.dialog .footer{padding:16px;border-top:1px solid var(--vvd-color-neutral-200)}.dialog .footer-header{margin-block-end:8px}.dialog .swatches-count{color:var(--_low-ink-color);font:var(--vvd-typography-base)}.dialog .palette{gap:var(--_color-swatches-gap, 8px)}vvd-hex-input{min-width:0;flex:1 1 auto}vvd-hex-input::part(input),vvd-hex-input>input{width:100%;height:100%;box-sizing:border-box;border:0 none;border-radius:8px;background-color:var(--vvd-color-canvas);box-shadow:inset 0 0 0 1px var(--vvd-color-neutral-500);font:var(--vvd-typography-base);outline:transparent;padding-inline:calc(1px*(40 + 4*clamp(-1,var(--vvd-size-density, 0),2))*.4)}vvd-hex-input::part(input):focus-visible,vvd-hex-input>input:focus-visible{box-shadow:0 0 0 4px color-mix(in srgb,var(--vvd-color-cta-500),transparent 85%),inset 0 0 0 3px var(--focus-stroke-gap-color, currentColor);outline:1px solid var(--focus-stroke-color, var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset, 0px));--focus-stroke-gap-color: transparent}vvd-hex-picker{width:auto;height:auto;gap:16px;inline-size:100%}vvd-hex-picker::part(saturation){border-radius:8px;block-size:120px}vvd-hex-picker::part(hue){flex:0 0 8px;border-radius:8px}vvd-hex-picker::part(hue-pointer){width:10px;height:10px}vvd-hex-picker::part(saturation-pointer){width:18px;height:18px}::part(popup-base){inline-size:max-content;min-block-size:var(--_color-picker-popup-min-inline-size, 264px);min-inline-size:var(--_color-picker-popup-min-inline-size, 264px)}.visually-hidden{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip:rect(0 0 0 0)}";
16
19
 
17
20
  // Clamps a value between an upper and lower bound.
18
21
  // We use ternary operators because it makes the minified code
@@ -602,7 +605,11 @@ const vcPickerTag = VC_HEX_PICKER_TAG;
602
605
  const vcInputTag = VC_HEX_INPUT_TAG;
603
606
  class ColorPicker extends WithContextualHelp(
604
607
  WithFeedback(
605
- WithErrorText(WithSuccessText(DelegatesAria(BaseColorPicker(VividElement))))
608
+ WithErrorText(
609
+ WithSuccessText(
610
+ DelegatesAria(TrappedFocus(BaseColorPicker(VividElement)))
611
+ )
612
+ )
606
613
  )
607
614
  ) {
608
615
  constructor() {
@@ -610,20 +617,6 @@ class ColorPicker extends WithContextualHelp(
610
617
  this.disableSavedColors = false;
611
618
  this.savedColors = [];
612
619
  this.maxSwatches = 6;
613
- this.copyIconName = "copy-2-line";
614
- this.#iconResetTimer = null;
615
- /**
616
- * @internal
617
- */
618
- this._copyValueToClipboard = async (value) => {
619
- try {
620
- await navigator.clipboard.writeText(value);
621
- this._setTemporaryCopyIcon("check-circle-line");
622
- } catch {
623
- alert(this.locale?.colorPicker?.copyErrorText);
624
- this._setTemporaryCopyIcon("error-line");
625
- }
626
- };
627
620
  /**
628
621
  * @internal
629
622
  */
@@ -633,14 +626,23 @@ class ColorPicker extends WithContextualHelp(
633
626
  }
634
627
  this.open = false;
635
628
  };
629
+ this.copyIconName = "copy-2-line";
630
+ this.#iconResetTimer = null;
636
631
  /**
637
632
  * @internal
638
633
  */
639
- this.#closeOnEscape = (e) => {
640
- if (e.key === "Escape") {
641
- this.open = false;
634
+ this._copyValueToClipboard = async (value) => {
635
+ try {
636
+ await navigator.clipboard.writeText(value);
637
+ this._setTemporaryCopyIcon("check-circle-line");
638
+ this._ariaLiveDescription = this.locale.colorPicker.copySuccessMessage(value);
639
+ } catch {
640
+ alert(this.locale.colorPicker.copyErrorText);
641
+ this._setTemporaryCopyIcon("error-line");
642
+ this._ariaLiveDescription = this.locale.colorPicker.copyErrorText;
642
643
  }
643
644
  };
645
+ this._ariaLiveDescription = "";
644
646
  }
645
647
  static {
646
648
  /**
@@ -666,6 +668,16 @@ class ColorPicker extends WithContextualHelp(
666
668
  handleChange() {
667
669
  this.$emit("change");
668
670
  }
671
+ /**
672
+ * @internal
673
+ */
674
+ openChanged(_oldValue, newValue) {
675
+ if (newValue && this.isConnected) {
676
+ requestAnimationFrame(() => {
677
+ this._refreshCanvasColor();
678
+ });
679
+ }
680
+ }
669
681
  /**
670
682
  * @internal
671
683
  */
@@ -686,6 +698,71 @@ class ColorPicker extends WithContextualHelp(
686
698
  _handleColorSaving() {
687
699
  this._saveCurrentColor();
688
700
  }
701
+ /**
702
+ * @internal
703
+ */
704
+ _saveCurrentColor() {
705
+ const value = this.value;
706
+ if (typeof value !== "string" || !isValidHexColor(value)) return;
707
+ const swatches = Array.isArray(this.savedColors) ? [...this.savedColors] : [];
708
+ const idx = swatches.findIndex((s) => s?.value === value);
709
+ if (idx !== -1) swatches.splice(idx, 1);
710
+ swatches.unshift({ value });
711
+ this.savedColors = swatches.slice(0, this._maxSwatchesNormalized);
712
+ this._setSavedColors(this.savedColors);
713
+ }
714
+ /**
715
+ * @internal
716
+ */
717
+ _loadSavedColors() {
718
+ try {
719
+ const savedColors = localStorage.getItem(this._savedColorsStorageKey);
720
+ if (!savedColors) return [];
721
+ const parsed = JSON.parse(savedColors);
722
+ if (!Array.isArray(parsed)) return [];
723
+ return parsed.filter(
724
+ (x) => x && typeof x.value === "string" && isValidHexColor(x.value)
725
+ ).map((x) => ({
726
+ value: x.value,
727
+ label: typeof x.label === "string" ? x.label : void 0
728
+ }));
729
+ } catch {
730
+ return [];
731
+ }
732
+ }
733
+ /**
734
+ * @internal
735
+ */
736
+ _setSavedColors(swatches) {
737
+ try {
738
+ localStorage.setItem(
739
+ this._savedColorsStorageKey,
740
+ JSON.stringify(swatches)
741
+ );
742
+ } catch {
743
+ }
744
+ }
745
+ /**
746
+ * Get all color swatches combined, both from swatches property and saved colors
747
+ * @internal
748
+ */
749
+ get allSwatches() {
750
+ const predefinedColors = Array.isArray(this.swatches) ? this.swatches : [];
751
+ const savedColors = Array.isArray(this.savedColors) ? this.savedColors : [];
752
+ const seen = /* @__PURE__ */ new Set();
753
+ const merged = [];
754
+ const appendIfUniqueAndValid = (swatch) => {
755
+ if (!swatch || typeof swatch.value !== "string") return;
756
+ if (!isValidHexColor(swatch.value) || seen.has(swatch.value)) return;
757
+ seen.add(swatch.value);
758
+ merged.push(swatch);
759
+ };
760
+ if (!this.disableSavedColors) {
761
+ savedColors.forEach(appendIfUniqueAndValid);
762
+ }
763
+ predefinedColors.forEach(appendIfUniqueAndValid);
764
+ return merged.slice(0, this._maxSwatchesNormalized);
765
+ }
689
766
  /**
690
767
  * @internal
691
768
  */
@@ -760,24 +837,11 @@ class ColorPicker extends WithContextualHelp(
760
837
  const path = e.composedPath?.();
761
838
  return !!(path && path.includes(el));
762
839
  }
763
- #iconResetTimer;
764
- /**
765
- * @internal
766
- */
767
- _setTemporaryCopyIcon(name, ms = 2e3) {
768
- this.copyIconName = name;
769
- if (this.#iconResetTimer) clearTimeout(this.#iconResetTimer);
770
- this.#iconResetTimer = setTimeout(() => {
771
- this.copyIconName = "copy-2-line";
772
- this.#iconResetTimer = null;
773
- }, ms);
774
- }
840
+ #closeOnPointerOutside;
775
841
  connectedCallback() {
776
842
  super.connectedCallback();
777
- this._refreshCanvasColor();
778
843
  this.savedColors = this._loadSavedColors();
779
844
  document.addEventListener("mousedown", this.#closeOnPointerOutside, true);
780
- document.addEventListener("keydown", this.#closeOnEscape);
781
845
  }
782
846
  disconnectedCallback() {
783
847
  super.disconnectedCallback();
@@ -786,74 +850,66 @@ class ColorPicker extends WithContextualHelp(
786
850
  this.#closeOnPointerOutside,
787
851
  true
788
852
  );
789
- document.removeEventListener("keydown", this.#closeOnEscape);
790
853
  }
791
- #closeOnPointerOutside;
792
- #closeOnEscape;
854
+ #iconResetTimer;
793
855
  /**
794
856
  * @internal
795
857
  */
796
- _saveCurrentColor() {
797
- const value = this.value;
798
- if (typeof value !== "string" || !isValidHexColor(value)) return;
799
- const swatches = Array.isArray(this.savedColors) ? [...this.savedColors] : [];
800
- const idx = swatches.findIndex((s) => s?.value === value);
801
- if (idx !== -1) swatches.splice(idx, 1);
802
- swatches.unshift({ value });
803
- this.savedColors = swatches.slice(0, this._maxSwatchesNormalized);
804
- this._setSavedColors(this.savedColors);
858
+ _setTemporaryCopyIcon(name, ms = 2e3) {
859
+ this.copyIconName = name;
860
+ if (this.#iconResetTimer) clearTimeout(this.#iconResetTimer);
861
+ this.#iconResetTimer = setTimeout(() => {
862
+ this.copyIconName = "copy-2-line";
863
+ this.#iconResetTimer = null;
864
+ }, ms);
805
865
  }
866
+ // --- Trapped focus ---
806
867
  /**
807
868
  * @internal
808
869
  */
809
- _loadSavedColors() {
810
- try {
811
- const savedColors = localStorage.getItem(this._savedColorsStorageKey);
812
- if (!savedColors) return [];
813
- const parsed = JSON.parse(savedColors);
814
- if (!Array.isArray(parsed)) return [];
815
- return parsed.filter(
816
- (x) => x && typeof x.value === "string" && isValidHexColor(x.value)
817
- ).map((x) => ({
818
- value: x.value,
819
- label: typeof x.label === "string" ? x.label : void 0
820
- }));
821
- } catch {
822
- return [];
870
+ _onBaseKeydown(event) {
871
+ if (this.open && handleEscapeKeyAndStopPropogation(event)) {
872
+ this.open = false;
873
+ return false;
823
874
  }
875
+ if (this._trappedFocus(event, () => this._focusableElsWithinDialog())) {
876
+ return false;
877
+ }
878
+ return true;
824
879
  }
825
880
  /**
826
881
  * @internal
827
882
  */
828
- _setSavedColors(swatches) {
829
- try {
830
- localStorage.setItem(
831
- this._savedColorsStorageKey,
832
- JSON.stringify(swatches)
883
+ _focusableElsWithinDialog() {
884
+ return this._popupEl.querySelectorAll(
885
+ 'button:not([role="gridcell"]), [data-vvd-component="button"], vwc-button:not([role="gridcell"])'
886
+ );
887
+ }
888
+ /**
889
+ * @internal
890
+ */
891
+ _handleCellKeydown(event, value, index, isSaveCell) {
892
+ if (event.key === "Tab") {
893
+ event.preventDefault();
894
+ const focusableEls = this._focusableElsWithinDialog();
895
+ const idx = Array.prototype.indexOf.call(
896
+ focusableEls,
897
+ event.currentTarget
833
898
  );
834
- } catch {
899
+ const nextIdx = event.shiftKey ? (idx - 1 + focusableEls.length) % focusableEls.length : (idx + 1) % focusableEls.length;
900
+ focusableEls[nextIdx]?.focus();
901
+ return false;
835
902
  }
903
+ return super._handleCellKeydown(event, value, index, isSaveCell);
836
904
  }
837
905
  /**
838
- * Get all color swatches combined, both from swatches property and saved colors
839
906
  * @internal
840
907
  */
841
- get allSwatches() {
842
- const predefinedColors = Array.isArray(this.swatches) ? this.swatches : [];
843
- const savedColors = Array.isArray(this.savedColors) ? this.savedColors : [];
844
- const seen = /* @__PURE__ */ new Set();
845
- const merged = [];
846
- const appendIfUniqueAndValid = (swatch) => {
847
- if (!swatch || typeof swatch.value !== "string") return;
848
- if (!isValidHexColor(swatch.value) || seen.has(swatch.value)) return;
849
- seen.add(swatch.value);
850
- merged.push(swatch);
851
- };
852
- if (!this.disableSavedColors) {
853
- savedColors.forEach(appendIfUniqueAndValid);
908
+ _handleSwatchSelection(value) {
909
+ if (this.value !== value) {
910
+ this._ariaLiveDescription = this.locale.colorPicker.selectionSuccessMessage(value);
854
911
  }
855
- predefinedColors.forEach(appendIfUniqueAndValid);
856
- return merged.slice(0, this._maxSwatchesNormalized);
912
+ super._handleSwatchSelection(value);
857
913
  }
858
914
  }
859
915
  __decorateClass([
@@ -881,6 +937,9 @@ __decorateClass([
881
937
  __decorateClass([
882
938
  observable
883
939
  ], ColorPicker.prototype, "copyIconName");
940
+ __decorateClass([
941
+ observable
942
+ ], ColorPicker.prototype, "_ariaLiveDescription");
884
943
 
885
944
  function renderTextField(textFieldTag, iconTag) {
886
945
  const getClasses = (_) => classNames("control");
@@ -911,6 +970,8 @@ function renderTextField(textFieldTag, iconTag) {
911
970
  >
912
971
  <button
913
972
  aria-label="${(x) => x.locale.colorPicker.pickerButtonLabel}"
973
+ aria-expanded="${(x) => x.open}"
974
+ aria-haspopup="dialog"
914
975
  class="button ${(x) => classNames(
915
976
  x._applyContrastClass(x._buttonColor) ? "contrast" : "",
916
977
  x.disabled ? "disabled" : ""
@@ -938,9 +999,9 @@ function renderTextField(textFieldTag, iconTag) {
938
999
  function renderPopupHeader(buttonTag, iconTag) {
939
1000
  return html`
940
1001
  <div class="header">
941
- <span class="header-title" id="color-picker-title">
1002
+ <h2 class="header-title" id="color-picker-title">
942
1003
  <slot name="popup-text">${(x) => x.locale.colorPicker.popupLabel}</slot>
943
- </span>
1004
+ </h2>
944
1005
  <${buttonTag} size="condensed"
945
1006
  aria-label="${(x) => x.locale.colorPicker.closeButtonLabel}"
946
1007
  @click="${(x) => x._handleCloseRequest()}">
@@ -949,7 +1010,7 @@ function renderPopupHeader(buttonTag, iconTag) {
949
1010
  </div>
950
1011
  `;
951
1012
  }
952
- function renderPopupBody(buttonTag, iconTag) {
1013
+ function renderPopupBody(buttonTag, iconTag, tooltipTag) {
953
1014
  return html`
954
1015
  <div class="body">
955
1016
  <${html.partial(vcPickerTag)}
@@ -965,24 +1026,37 @@ function renderPopupBody(buttonTag, iconTag) {
965
1026
  color="${(x) => x.value}"
966
1027
  @color-changed="${(x, c) => x._onPickerColorChanged(c.event)}"
967
1028
  ${ref("_vcHexInputEl")}
968
- ></${html.partial(vcInputTag)}>
969
- <${buttonTag} size="normal"
970
- aria-label="${(x) => x.locale.colorPicker.copyButtonLabel}"
971
- @click="${(x) => x._copyValueToClipboard(x.value)}">
972
- <${iconTag} slot="icon" name="${(x) => x.copyIconName}"></${iconTag}>
973
- </${buttonTag}>
1029
+ >
1030
+ <input name="hex-code-input" aria-label="${(x) => x.locale.colorPicker.hexInputLabel}"
1031
+ placeholder="${(x) => x.placeholder}"
1032
+ @blur="${(x, c) => c.event.stopImmediatePropagation()}"
1033
+ part="input">
1034
+ </${html.partial(vcInputTag)}>
1035
+ <${tooltipTag} placement="top"
1036
+ text="${(x) => x.locale.colorPicker.copyButtonLabel}"
1037
+ exportparts="vvd-theme-alternate">
1038
+ <${buttonTag}
1039
+ slot="anchor" size="normal"
1040
+ aria-label="${(x) => x.locale.colorPicker.copyButtonLabel}"
1041
+ @click="${(x) => x._copyValueToClipboard(x.value)}">
1042
+ <${iconTag} slot="icon" name="${(x) => x.copyIconName}"></${iconTag}>
1043
+ </${buttonTag}>
1044
+ </${tooltipTag}>
974
1045
  </div>
975
1046
  </div>
976
1047
  `;
977
1048
  }
978
- function renderPopupFooter(buttonTag, iconTag) {
1049
+ function renderPopupFooter(buttonTag, iconTag, tooltipTag) {
979
1050
  return html`
980
1051
  <div class="footer">
981
- <span class="footer-title" id="color-picker-footer-title"
982
- ><slot name="swatches-text"
983
- >${(x) => x.locale.colorPicker.swatchesLabel}</slot
984
- ></span
985
- >
1052
+ <div class="footer-header">
1053
+ <span class="footer-title" id="color-picker-footer-title"
1054
+ ><slot name="swatches-text"
1055
+ >${(x) => x.locale.colorPicker.swatchesLabel}</slot
1056
+ ></span
1057
+ >
1058
+ ${when((x) => !x.disableSavedColors, renderSwatchesCount())}
1059
+ </div>
986
1060
  <div
987
1061
  class="palette"
988
1062
  role="grid"
@@ -993,41 +1067,62 @@ function renderPopupFooter(buttonTag, iconTag) {
993
1067
  >
994
1068
  ${repeat(
995
1069
  (x) => x.allSwatches,
996
- (x) => x._renderColorSwatch(iconTag),
1070
+ (x) => x._renderColorSwatch(iconTag, tooltipTag),
997
1071
  { positioning: true }
998
1072
  )}
999
1073
  ${when(
1000
1074
  (x) => !x.disableSavedColors,
1001
1075
  html`
1002
- <${buttonTag}
1003
- appearance="outlined"
1004
- size="super-condensed"
1005
- role="gridcell"
1006
- tabindex="${(x) => x.allSwatches.length ? "-1" : "0"}"
1007
- aria-label="${(x) => x.locale.colorPicker.saveButtonLabel}"
1008
- @click="${(x) => x._saveCurrentColor()}"
1009
- @keydown="${(x, c) => x._handleCellKeydown(
1076
+ <${tooltipTag} placement="top"
1077
+ text="${(x) => x.locale.colorPicker.saveButtonLabel}"
1078
+ exportparts="vvd-theme-alternate">
1079
+ <${buttonTag}
1080
+ slot="anchor"
1081
+ appearance="outlined"
1082
+ size="super-condensed"
1083
+ role="gridcell"
1084
+ tabindex="${(x) => x.allSwatches.length ? "-1" : "0"}"
1085
+ aria-label="${(x) => x.locale.colorPicker.saveButtonLabel}"
1086
+ @click="${(x) => x._saveCurrentColor()}"
1087
+ @keydown="${(x, c) => x._handleCellKeydown(
1010
1088
  c.event,
1011
1089
  x.value,
1012
1090
  x.allSwatches.length,
1013
1091
  true
1014
1092
  )}">
1015
- >
1016
- <${iconTag} slot="icon" name="plus-line"></${iconTag}>
1017
- </${buttonTag}>
1018
- `
1093
+ <${iconTag} slot="icon" name="plus-line"></${iconTag}>
1094
+ </${buttonTag}>
1095
+ </${tooltipTag}>
1096
+ `
1019
1097
  )}
1020
1098
  </div>
1021
1099
  </div>
1022
1100
  `;
1023
1101
  }
1102
+ function renderSwatchesCount() {
1103
+ return html`
1104
+ <span
1105
+ id="swatches-count"
1106
+ class="swatches-count"
1107
+ aria-label="${(x) => x.locale.colorPicker.maxSwatchesMessage(
1108
+ x.allSwatches.length,
1109
+ x.maxSwatches
1110
+ )}"
1111
+ >${(x) => `${x.allSwatches.length}/${x.maxSwatches}`}</span
1112
+ >
1113
+ `;
1114
+ }
1024
1115
  const ColorPickerTemplate = (context) => {
1025
1116
  const textFieldTag = context.tagFor(TextField);
1026
1117
  const iconTag = context.tagFor(Icon);
1027
1118
  const popupTag = context.tagFor(Popup);
1028
1119
  const buttonTag = context.tagFor(Button);
1120
+ const tooltipTag = context.tagFor(Tooltip);
1029
1121
  return html`
1030
- <div class="base">
1122
+ <div class="base" @keydown="${(x, { event }) => x._onBaseKeydown(event)}">
1123
+ <span aria-live="assertive" aria-relevant="text" class="visually-hidden">
1124
+ ${(x) => x._ariaLiveDescription}
1125
+ </span>
1031
1126
  ${renderTextField(textFieldTag, iconTag)}
1032
1127
  <${popupTag}
1033
1128
  :open="${(x) => x.open}"
@@ -1040,10 +1135,10 @@ const ColorPickerTemplate = (context) => {
1040
1135
  aria-modal="true"
1041
1136
  aria-labelledby="color-picker-title">
1042
1137
  ${renderPopupHeader(buttonTag, iconTag)}
1043
- ${renderPopupBody(buttonTag, iconTag)}
1138
+ ${renderPopupBody(buttonTag, iconTag, tooltipTag)}
1044
1139
  ${when(
1045
1140
  (x) => !x.disableSavedColors || x.allSwatches.length > 0,
1046
- renderPopupFooter(buttonTag, iconTag)
1141
+ renderPopupFooter(buttonTag, iconTag, tooltipTag)
1047
1142
  )}
1048
1143
  </div>
1049
1144
  </${popupTag}>
@@ -1060,6 +1155,7 @@ const colorPickerDefinition = defineVividComponent(
1060
1155
  iconDefinition,
1061
1156
  textFieldDefinition,
1062
1157
  buttonDefinition,
1158
+ tooltipDefinition,
1063
1159
  feedbackMessageDefinition
1064
1160
  ],
1065
1161
  {