@synergy-design-system/mcp 2.6.1 → 2.8.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 (78) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/utilities/storybook/scraper.js +14 -3
  3. package/metadata/checksum.txt +1 -1
  4. package/metadata/packages/components/components/syn-alert/component.styles.ts +126 -26
  5. package/metadata/packages/components/components/syn-alert/component.ts +1 -2
  6. package/metadata/packages/components/components/syn-checkbox/component.angular.ts +13 -0
  7. package/metadata/packages/components/components/syn-checkbox/component.styles.ts +99 -39
  8. package/metadata/packages/components/components/syn-checkbox/component.ts +13 -10
  9. package/metadata/packages/components/components/syn-checkbox/component.vue +5 -0
  10. package/metadata/packages/components/components/syn-combobox/component.angular.ts +13 -0
  11. package/metadata/packages/components/components/syn-combobox/component.styles.ts +216 -193
  12. package/metadata/packages/components/components/syn-combobox/component.ts +68 -39
  13. package/metadata/packages/components/components/syn-combobox/component.vue +5 -0
  14. package/metadata/packages/components/components/syn-file/component.angular.ts +13 -0
  15. package/metadata/packages/components/components/syn-file/component.styles.ts +20 -3
  16. package/metadata/packages/components/components/syn-file/component.ts +19 -5
  17. package/metadata/packages/components/components/syn-file/component.vue +5 -0
  18. package/metadata/packages/components/components/syn-input/component.ts +1 -2
  19. package/metadata/packages/components/components/syn-popup/component.styles.ts +24 -17
  20. package/metadata/packages/components/components/syn-popup/component.ts +1 -2
  21. package/metadata/packages/components/components/syn-radio/component.angular.ts +13 -0
  22. package/metadata/packages/components/components/syn-radio/component.styles.ts +91 -29
  23. package/metadata/packages/components/components/syn-radio/component.ts +19 -10
  24. package/metadata/packages/components/components/syn-radio/component.vue +5 -0
  25. package/metadata/packages/components/components/syn-radio-group/component.styles.ts +30 -9
  26. package/metadata/packages/components/components/syn-radio-group/component.ts +61 -32
  27. package/metadata/packages/components/components/syn-range/component.angular.ts +13 -0
  28. package/metadata/packages/components/components/syn-range/component.styles.ts +27 -3
  29. package/metadata/packages/components/components/syn-range/component.ts +17 -5
  30. package/metadata/packages/components/components/syn-range/component.vue +5 -0
  31. package/metadata/packages/components/components/syn-select/component.angular.ts +13 -0
  32. package/metadata/packages/components/components/syn-select/component.styles.ts +222 -151
  33. package/metadata/packages/components/components/syn-select/component.ts +30 -15
  34. package/metadata/packages/components/components/syn-select/component.vue +5 -0
  35. package/metadata/packages/components/components/syn-switch/component.angular.ts +13 -0
  36. package/metadata/packages/components/components/syn-switch/component.styles.ts +145 -63
  37. package/metadata/packages/components/components/syn-switch/component.ts +16 -4
  38. package/metadata/packages/components/components/syn-switch/component.vue +5 -0
  39. package/metadata/packages/components/components/syn-textarea/component.styles.ts +55 -27
  40. package/metadata/packages/components/components/syn-textarea/component.ts +1 -3
  41. package/metadata/packages/components/components/syn-tooltip/component.styles.ts +15 -10
  42. package/metadata/packages/components/components/syn-tooltip/component.ts +13 -5
  43. package/metadata/packages/components/components/syn-validate/component.angular.ts +9 -0
  44. package/metadata/packages/components/components/syn-validate/component.react.ts +8 -0
  45. package/metadata/packages/components/components/syn-validate/component.ts +106 -8
  46. package/metadata/packages/components/components/syn-validate/component.vue +9 -0
  47. package/metadata/packages/components/static/CHANGELOG.md +43 -0
  48. package/metadata/packages/tokens/CHANGELOG.md +33 -0
  49. package/metadata/packages/tokens/dark.css +7 -1
  50. package/metadata/packages/tokens/index.js +31 -1
  51. package/metadata/packages/tokens/light.css +7 -1
  52. package/metadata/packages/tokens/sick2018_dark.css +7 -1
  53. package/metadata/packages/tokens/sick2018_light.css +7 -1
  54. package/metadata/packages/tokens/sick2025_dark.css +7 -1
  55. package/metadata/packages/tokens/sick2025_light.css +7 -1
  56. package/metadata/static/components/syn-checkbox/docs.md +36 -0
  57. package/metadata/static/components/syn-combobox/docs.md +138 -0
  58. package/metadata/static/components/syn-file/docs.md +24 -0
  59. package/metadata/static/components/syn-input/docs.md +1 -1
  60. package/metadata/static/components/syn-radio/docs.md +21 -0
  61. package/metadata/static/components/syn-radio-group/docs.md +46 -0
  62. package/metadata/static/components/syn-range/docs.md +19 -0
  63. package/metadata/static/components/syn-select/docs.md +81 -0
  64. package/metadata/static/components/syn-switch/docs.md +22 -0
  65. package/metadata/static/components/syn-textarea/docs.md +1 -1
  66. package/metadata/static/components/syn-tooltip/docs.md +73 -0
  67. package/metadata/static/components/syn-validate/docs.md +33 -6
  68. package/package.json +4 -4
  69. package/metadata/packages/components/components/syn-alert/component.custom.styles.ts +0 -136
  70. package/metadata/packages/components/components/syn-checkbox/component.custom.styles.ts +0 -86
  71. package/metadata/packages/components/components/syn-combobox/component.custom.styles.ts +0 -122
  72. package/metadata/packages/components/components/syn-popup/component.custom.styles.ts +0 -18
  73. package/metadata/packages/components/components/syn-radio/component.custom.styles.ts +0 -86
  74. package/metadata/packages/components/components/syn-radio-group/component.custom.styles.ts +0 -25
  75. package/metadata/packages/components/components/syn-select/component.custom.styles.ts +0 -175
  76. package/metadata/packages/components/components/syn-switch/component.custom.styles.ts +0 -141
  77. package/metadata/packages/components/components/syn-textarea/component.custom.styles.ts +0 -48
  78. package/metadata/packages/components/components/syn-tooltip/component.custom.styles.ts +0 -13
@@ -1,104 +1,125 @@
1
- /* eslint-disable */
2
1
  import { css } from 'lit';
3
2
 
4
3
  export default css`
5
- /* stylelint-disable */
4
+ /* stylelint-disable no-descending-specificity */
6
5
  :host {
7
6
  display: block;
8
7
  }
9
8
 
10
9
  :host(:focus-visible) {
11
- outline: 0px;
10
+ outline: 0;
12
11
  }
13
12
 
14
13
  .radio {
14
+ align-items: flex-start;
15
+ color: var(--syn-input-label-color);
16
+ cursor: pointer;
15
17
  display: inline-flex;
16
- align-items: top;
17
18
  font-family: var(--syn-input-font-family);
18
19
  font-size: var(--syn-input-font-size-medium);
19
20
  font-weight: var(--syn-input-font-weight);
20
- color: var(--syn-input-label-color);
21
21
  vertical-align: middle;
22
- cursor: pointer;
23
22
  }
24
23
 
25
24
  .radio--small {
26
25
  --toggle-size: var(--syn-toggle-size-small);
26
+
27
27
  font-size: var(--syn-input-font-size-small);
28
28
  }
29
29
 
30
30
  .radio--medium {
31
31
  --toggle-size: var(--syn-toggle-size-medium);
32
+
32
33
  font-size: var(--syn-input-font-size-medium);
33
34
  }
34
35
 
35
36
  .radio--large {
36
37
  --toggle-size: var(--syn-toggle-size-large);
38
+
37
39
  font-size: var(--syn-input-font-size-large);
38
40
  }
39
41
 
40
42
  .radio__checked-icon {
41
43
  display: inline-flex;
42
- width: var(--toggle-size);
43
44
  height: var(--toggle-size);
45
+
46
+ /**
47
+ * #920: The new icons are instances in figma.
48
+ * The width of the system icon is 12px x 12px, so there is no inner padding.
49
+ * To accommodate for this, we need to set the width and height of the icon to 50% to get the same result as before.
50
+ */
51
+ scale: 0.5;
52
+ width: var(--toggle-size);
53
+
44
54
  }
45
55
 
46
56
  .radio__control {
47
- flex: 0 0 auto;
48
- position: relative;
49
- display: inline-flex;
50
57
  align-items: center;
51
- justify-content: center;
52
- width: var(--toggle-size);
53
- height: var(--toggle-size);
58
+ background-color: var(--syn-input-background-color);
54
59
  border: solid var(--syn-input-border-width) var(--syn-input-border-color);
55
60
  border-radius: 50%;
56
- background-color: var(--syn-input-background-color);
57
61
  color: transparent;
62
+ display: inline-flex;
63
+ flex: 0 0 auto;
64
+ height: var(--toggle-size);
65
+ justify-content: center;
66
+ position: relative;
58
67
  transition:
59
68
  var(--syn-transition-fast) border-color,
60
69
  var(--syn-transition-fast) background-color,
61
70
  var(--syn-transition-fast) color,
62
71
  var(--syn-transition-fast) box-shadow;
72
+ width: var(--toggle-size);
63
73
  }
64
74
 
65
75
  .radio__input {
66
- position: absolute;
76
+ margin: 0;
67
77
  opacity: 0;
68
78
  padding: 0;
69
- margin: 0;
70
79
  pointer-events: none;
80
+ position: absolute;
71
81
  }
72
82
 
73
83
  /* Hover */
74
- .radio:not(.radio--checked):not(.radio--disabled) .radio__control:hover {
84
+ .radio:not(.radio--checked):not(.radio--disabled):not(.radio--readonly):hover .radio__control {
75
85
  border-color: var(--syn-input-border-color-hover);
76
- background-color: var(--syn-input-background-color-hover);
77
86
  }
78
87
 
79
88
  /* Checked */
80
89
  .radio--checked .radio__control {
90
+ background-color: var(--syn-interactive-emphasis-color);
91
+ border-color: var(--syn-interactive-emphasis-color);
81
92
  color: var(--syn-color-neutral-0);
82
- border-color: var(--syn-color-primary-600);
83
- background-color: var(--syn-color-primary-600);
84
93
  }
85
94
 
86
95
  /* Checked + hover */
87
- .radio.radio--checked:not(.radio--disabled) .radio__control:hover {
88
- border-color: var(--syn-color-primary-500);
89
- background-color: var(--syn-color-primary-500);
96
+ .radio.radio--checked:not(.radio--disabled):not(.radio--readonly):hover .radio__control {
97
+ background-color: var(--syn-interactive-emphasis-color-hover);
98
+ border-color: var(--syn-interactive-emphasis-color-hover);
99
+ }
100
+
101
+ /* Checked + active */
102
+ .radio.radio--checked:not(.radio--disabled):not(.radio--readonly):active .radio__control {
103
+ background-color: var(--syn-interactive-emphasis-color-active);
104
+ border-color: var(--syn-interactive-emphasis-color-active);
105
+ }
106
+
107
+ /* Not-Checked + active */
108
+ .radio:not(.radio--checked):not(.radio--disabled):not(.radio--readonly):active .radio__control {
109
+ border-color: var(--syn-input-border-color-active);
90
110
  }
91
111
 
92
112
  /* Checked + focus */
93
113
  :host(:focus-visible) .radio__control {
94
114
  outline: var(--syn-focus-ring);
95
- outline-offset: var(--syn-focus-ring-offset);
115
+ outline-offset: var(--syn-focus-ring-width);
96
116
  }
97
117
 
98
- /* Disabled */
99
118
  .radio--disabled {
100
- opacity: 0.5;
101
119
  cursor: not-allowed;
120
+
121
+ /** #429: Use token for opacity */
122
+ opacity: var(--syn-input-disabled-opacity);
102
123
  }
103
124
 
104
125
  /* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */
@@ -106,12 +127,53 @@ export default css`
106
127
  opacity: 0;
107
128
  }
108
129
 
130
+ /* Fix#456: Multi line radio fixes */
109
131
  .radio__label {
110
- display: inline-block;
132
+ align-self: center;
111
133
  color: var(--syn-input-label-color);
134
+ display: inline-block;
112
135
  line-height: var(--toggle-size);
113
- margin-inline-start: 0.5em;
136
+ margin-inline-start: var(--syn-spacing-x-small);
137
+ margin-top: -1px;
114
138
  user-select: none;
115
- -webkit-user-select: none;
139
+ }
140
+
141
+ .radio--small .radio__label {
142
+ font: var(--syn-body-small-regular);
143
+ margin-inline-start: var(--syn-spacing-x-small);
144
+ }
145
+
146
+ .radio--medium .radio__label {
147
+ font: var(--syn-body-medium-regular);
148
+ }
149
+
150
+ .radio--large .radio__label {
151
+ font: var(--syn-body-large-regular);
152
+ margin-inline-start: var(--syn-spacing-x-small);
153
+ }
154
+
155
+ /* /Fix#456 */
156
+
157
+ /**
158
+ * #1174: Readonly state
159
+ */
160
+ .radio.radio--readonly {
161
+ cursor: default;
162
+ }
163
+
164
+ .radio.radio--readonly .radio__label {
165
+ user-select: auto;
166
+ }
167
+
168
+ .radio.radio--readonly .radio__control {
169
+ background: var(--syn-input-readonly-background-color);
170
+ border-color: var(--syn-input-readonly-background-color);
171
+ color: var(--syn-readonly-indicator-color);
172
+ cursor: default;
173
+ }
174
+
175
+ :host(:focus) .radio--readonly .radio__control {
176
+ outline: var(--syn-focus-ring);
177
+ outline-offset: var(--syn-focus-ring-width);
116
178
  }
117
179
  `;
@@ -1,14 +1,12 @@
1
- /* eslint-disable */
2
1
  import { classMap } from 'lit/directives/class-map.js';
3
2
  import { html } from 'lit';
3
+ import type { CSSResultGroup } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  import { watch } from '../../internal/watch.js';
6
6
  import componentStyles from '../../styles/component.styles.js';
7
7
  import SynergyElement from '../../internal/synergy-element.js';
8
8
  import SynIcon from '../icon/icon.component.js';
9
9
  import styles from './radio.styles.js';
10
- import customStyles from './radio.custom.styles.js';
11
- import type { CSSResultGroup } from 'lit';
12
10
  import { enableDefaultSettings } from '../../utilities/defaultSettings/decorator.js';
13
11
 
14
12
  /**
@@ -32,10 +30,12 @@ import { enableDefaultSettings } from '../../utilities/defaultSettings/decorator
32
30
  */
33
31
  @enableDefaultSettings('SynRadio')
34
32
  export default class SynRadio extends SynergyElement {
35
- static styles: CSSResultGroup = [componentStyles, styles, customStyles];
33
+ static styles: CSSResultGroup = [componentStyles, styles];
34
+
36
35
  static dependencies = { 'syn-icon': SynIcon };
37
36
 
38
37
  @state() checked = false;
38
+
39
39
  @state() protected hasFocus = false;
40
40
 
41
41
  /** The radio's value. When selected, the radio group will receive this value. */
@@ -48,7 +48,10 @@ export default class SynRadio extends SynergyElement {
48
48
  @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
49
49
 
50
50
  /** Disables the radio. */
51
- @property({ type: Boolean, reflect: true }) disabled = false;
51
+ @property({ reflect: true, type: Boolean }) disabled = false;
52
+
53
+ /** Sets the radio to a readonly state. */
54
+ @property({ reflect: true, type: Boolean }) readonly = false;
52
55
 
53
56
  constructor() {
54
57
  super();
@@ -68,6 +71,11 @@ export default class SynRadio extends SynergyElement {
68
71
  };
69
72
 
70
73
  private handleClick = () => {
74
+ if (this.readonly) {
75
+ this.focus();
76
+ return;
77
+ }
78
+
71
79
  if (!this.disabled) {
72
80
  this.checked = true;
73
81
  }
@@ -81,7 +89,7 @@ export default class SynRadio extends SynergyElement {
81
89
  private setInitialAttributes() {
82
90
  this.setAttribute('role', 'radio');
83
91
  this.setAttribute('tabindex', '-1');
84
- this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
92
+ this.setAttribute('aria-disabled', (this.disabled || this.readonly) ? 'true' : 'false');
85
93
  }
86
94
 
87
95
  @watch('checked')
@@ -90,9 +98,9 @@ export default class SynRadio extends SynergyElement {
90
98
  this.setAttribute('tabindex', this.checked ? '0' : '-1');
91
99
  }
92
100
 
93
- @watch('disabled', { waitUntilFirstUpdate: true })
101
+ @watch(['disabled', 'readonly'], { waitUntilFirstUpdate: true })
94
102
  handleDisabledChange() {
95
- this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
103
+ this.setAttribute('aria-disabled', (this.disabled || this.readonly) ? 'true' : 'false');
96
104
  }
97
105
 
98
106
  render() {
@@ -104,9 +112,10 @@ export default class SynRadio extends SynergyElement {
104
112
  'radio--checked': this.checked,
105
113
  'radio--disabled': this.disabled,
106
114
  'radio--focused': this.hasFocus,
107
- 'radio--small': this.size === 'small',
115
+ 'radio--large': this.size === 'large',
108
116
  'radio--medium': this.size === 'medium',
109
- 'radio--large': this.size === 'large'
117
+ 'radio--readonly': this.readonly,
118
+ 'radio--small': this.size === 'small',
110
119
  })}
111
120
  >
112
121
  <span part="${`control${this.checked ? ' control--checked' : ''}`}" class="radio__control">
@@ -57,6 +57,11 @@ attribute can typically be omitted.
57
57
  * Disables the radio.
58
58
  */
59
59
  disabled?: SynRadio['disabled'];
60
+
61
+ /**
62
+ * Sets the radio to a readonly state.
63
+ */
64
+ readonly?: SynRadio['readonly'];
60
65
  }>();
61
66
 
62
67
  // Make sure prop binding only forwards the props that are actually there.
@@ -1,17 +1,27 @@
1
- /* eslint-disable */
2
1
  import { css } from 'lit';
3
2
 
4
3
  export default css`
5
- /* stylelint-disable */
6
4
  :host {
7
5
  display: block;
8
6
  }
9
7
 
8
+ :host([data-user-invalid]) {
9
+ --syn-input-border-color: var(--syn-input-border-color-focus-error);
10
+ --syn-input-border-color-hover: var(--syn-input-border-color-focus-error);
11
+ --syn-color-primary-600: var(--syn-input-border-color-focus-error);
12
+ --syn-color-primary-900: var(--syn-color-error-900);
13
+ --syn-color-primary-950: var(--syn-color-error-950);
14
+ --syn-color-neutral-1000: var(--syn-input-border-color-focus-error);
15
+ --syn-interactive-emphasis-color: var(--syn-input-border-color-focus-error);
16
+ --syn-interactive-emphasis-color-hover: var(--syn-input-border-color-focus-error);
17
+ --syn-interactive-emphasis-color-active: var(--syn-input-border-color-focus-error);
18
+ }
19
+
10
20
  .form-control {
11
- position: relative;
12
21
  border: none;
13
- padding: 0;
14
22
  margin: 0;
23
+ padding: 0;
24
+ position: relative;
15
25
  }
16
26
 
17
27
  .form-control__label {
@@ -24,14 +34,25 @@ export default css`
24
34
  }
25
35
 
26
36
  .visually-hidden {
27
- position: absolute;
28
- width: 1px;
37
+ border: 0;
38
+ /* stylelint-disable-next-line property-no-deprecated */
39
+ clip: rect(0, 0, 0, 0);
29
40
  height: 1px;
30
- padding: 0;
31
41
  margin: -1px;
32
42
  overflow: hidden;
33
- clip: rect(0, 0, 0, 0);
43
+ padding: 0;
44
+ position: absolute;
34
45
  white-space: nowrap;
35
- border: 0;
46
+ width: 1px;
47
+ }
48
+
49
+ .form-control-input {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: var(--syn-spacing-x-small);
53
+ }
54
+
55
+ .form-control--has-help-text.form-control--radio-group .form-control__help-text {
56
+ margin-top: var(--syn-spacing-x-small);
36
57
  }
37
58
  `;
@@ -1,23 +1,22 @@
1
- /* eslint-disable */
1
+ /* eslint-disable @typescript-eslint/no-floating-promises */
2
+ /* eslint-disable no-param-reassign */
2
3
  import { classMap } from 'lit/directives/class-map.js';
4
+ import { html } from 'lit';
5
+ import { property, query, state } from 'lit/decorators.js';
6
+ import type { CSSResultGroup } from 'lit';
3
7
  import {
4
- customErrorValidityState,
5
8
  FormControlController,
9
+ customErrorValidityState,
6
10
  validValidityState,
7
- valueMissingValidityState
11
+ valueMissingValidityState,
8
12
  } from '../../internal/form.js';
9
13
  import { HasSlotController } from '../../internal/slot.js';
10
- import { html } from 'lit';
11
- import { property, query, state } from 'lit/decorators.js';
12
14
  import { watch } from '../../internal/watch.js';
13
15
  import componentStyles from '../../styles/component.styles.js';
14
16
  import formControlStyles from '../../styles/form-control.styles.js';
15
- import formControlCustomStyles from '../../styles/form-control.custom.styles.js';
16
17
  import SynergyElement from '../../internal/synergy-element.js';
17
18
  import SynButtonGroup from '../button-group/button-group.component.js';
18
19
  import styles from './radio-group.styles.js';
19
- import customStyles from './radio-group.custom.styles.js';
20
- import type { CSSResultGroup } from 'lit';
21
20
  import type { SynergyFormControl } from '../../internal/synergy-element.js';
22
21
  import type SynRadio from '../radio/radio.js';
23
22
  import type SynRadioButton from '../radio-button/radio-button.js';
@@ -49,19 +48,26 @@ import { enableDefaultSettings } from '../../utilities/defaultSettings/decorator
49
48
  */
50
49
  @enableDefaultSettings('SynRadioGroup')
51
50
  export default class SynRadioGroup extends SynergyElement implements SynergyFormControl {
52
- static styles: CSSResultGroup = [componentStyles, formControlStyles, styles, formControlCustomStyles, customStyles];
51
+ static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
52
+
53
53
  static dependencies = { 'syn-button-group': SynButtonGroup };
54
54
 
55
55
  protected readonly formControlController = new FormControlController(this);
56
+
56
57
  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
58
+
57
59
  private customValidityMessage = '';
60
+
58
61
  private validationTimeout: number;
59
62
 
60
63
  @query('slot:not([name])') defaultSlot: HTMLSlotElement;
64
+
61
65
  @query('.radio-group__validation-input') validationInput: HTMLInputElement;
62
66
 
63
67
  @state() private hasButtonGroup = false;
68
+
64
69
  @state() private errorMessage = '';
70
+
65
71
  @state() defaultValue: string | number = '';
66
72
 
67
73
  /**
@@ -90,7 +96,7 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
90
96
  @property({ reflect: true }) form = '';
91
97
 
92
98
  /** Ensures a child radio is checked before allowing the containing form to submit. */
93
- @property({ type: Boolean, reflect: true }) required = false;
99
+ @property({ reflect: true, type: Boolean }) required = false;
94
100
 
95
101
  /** Gets the validity state object */
96
102
  get validity() {
@@ -99,7 +105,9 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
99
105
 
100
106
  if (hasCustomValidityMessage) {
101
107
  return customErrorValidityState;
102
- } else if (isRequiredAndEmpty) {
108
+ }
109
+
110
+ if (isRequiredAndEmpty) {
103
111
  return valueMissingValidityState;
104
112
  }
105
113
 
@@ -113,7 +121,9 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
113
121
 
114
122
  if (hasCustomValidityMessage) {
115
123
  return this.customValidityMessage;
116
- } else if (isRequiredAndEmpty) {
124
+ }
125
+
126
+ if (isRequiredAndEmpty) {
117
127
  return this.validationInput.validationMessage;
118
128
  }
119
129
 
@@ -138,12 +148,15 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
138
148
  const radios = this.getAllRadios();
139
149
  const oldValue = this.value;
140
150
 
141
- if (!target || target.disabled) {
151
+ // #1174: If we have a radio, also do nothing if the radio is readonly
152
+ if (!target || target.disabled || (target as SynRadio).readonly) {
142
153
  return;
143
154
  }
144
155
 
145
156
  this.value = target.value;
146
- radios.forEach(radio => (radio.checked = radio === target));
157
+ radios.forEach(radio => {
158
+ radio.checked = radio === target;
159
+ });
147
160
 
148
161
  if (this.value !== oldValue) {
149
162
  this.emit('syn-change');
@@ -156,21 +169,26 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
156
169
  return;
157
170
  }
158
171
 
159
- const radios = this.getAllRadios().filter(radio => !radio.disabled);
160
- const checkedRadio = radios.find(radio => radio.checked) ?? radios[0];
172
+ // #1174: Filter out elements that are either disabled or readonly
173
+ const availableRadios = this.getAllRadios().filter(radio => (!radio.disabled && !(radio as SynRadio).readonly));
174
+ const checkedRadio = availableRadios.find(radio => radio.checked) ?? availableRadios[0];
175
+
176
+ // eslint-disable-next-line no-nested-ternary
161
177
  const incr = event.key === ' ' ? 0 : ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;
162
178
  const oldValue = this.value;
163
- let index = radios.indexOf(checkedRadio) + incr;
179
+ let index = availableRadios.indexOf(checkedRadio) + incr;
164
180
 
165
181
  if (index < 0) {
166
- index = radios.length - 1;
182
+ index = availableRadios.length - 1;
167
183
  }
168
184
 
169
- if (index > radios.length - 1) {
185
+ if (index > availableRadios.length - 1) {
170
186
  index = 0;
171
187
  }
172
188
 
173
- this.getAllRadios().forEach(radio => {
189
+ const allRadios = this.getAllRadios();
190
+
191
+ allRadios.forEach(radio => {
174
192
  radio.checked = false;
175
193
 
176
194
  if (!this.hasButtonGroup) {
@@ -178,14 +196,19 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
178
196
  }
179
197
  });
180
198
 
181
- this.value = radios[index].value;
182
- radios[index].checked = true;
199
+ // #1175: If all radios are readonly, skip and focus the first one and do not change the value
200
+ if (!availableRadios[index]) {
201
+ return;
202
+ }
203
+
204
+ this.value = availableRadios[index].value;
205
+ availableRadios[index].checked = true;
183
206
 
184
207
  if (!this.hasButtonGroup) {
185
- radios[index].setAttribute('tabindex', '0');
186
- radios[index].focus();
208
+ availableRadios[index].setAttribute('tabindex', '0');
209
+ availableRadios[index].focus();
187
210
  } else {
188
- radios[index].shadowRoot!.querySelector('button')!.focus();
211
+ availableRadios[index].shadowRoot!.querySelector('button')!.focus();
189
212
  }
190
213
 
191
214
  if (this.value !== oldValue) {
@@ -214,7 +237,7 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
214
237
  await radio.updateComplete;
215
238
  radio.checked = radio.value === this.value;
216
239
  radio.size = this.size;
217
- })
240
+ }),
218
241
  );
219
242
 
220
243
  this.hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'syn-radio-button');
@@ -262,7 +285,9 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
262
285
 
263
286
  private updateCheckedRadio() {
264
287
  const radios = this.getAllRadios();
265
- radios.forEach(radio => (radio.checked = radio.value === this.value));
288
+ radios.forEach(radio => {
289
+ radio.checked = radio.value === this.value;
290
+ });
266
291
  this.formControlController.setValidity(this.validity.valid);
267
292
  }
268
293
 
@@ -309,7 +334,9 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
309
334
  // Show the browser's constraint validation message
310
335
  this.validationInput.hidden = false;
311
336
  this.validationInput.reportValidity();
312
- this.validationTimeout = setTimeout(() => (this.validationInput.hidden = true), 10000) as unknown as number;
337
+ this.validationTimeout = setTimeout(() => {
338
+ this.validationInput.hidden = true;
339
+ }, 10000) as unknown as number;
313
340
  }
314
341
 
315
342
  return isValid;
@@ -338,6 +365,7 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
338
365
  }
339
366
 
340
367
  render() {
368
+ /* eslint-disable @typescript-eslint/unbound-method */
341
369
  const hasLabelSlot = this.hasSlotController.test('label');
342
370
  const hasHelpTextSlot = this.hasSlotController.test('help-text');
343
371
  const hasLabel = this.label ? true : !!hasLabelSlot;
@@ -351,12 +379,12 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
351
379
  part="form-control"
352
380
  class=${classMap({
353
381
  'form-control': true,
354
- 'form-control--small': this.size === 'small',
355
- 'form-control--medium': this.size === 'medium',
382
+ 'form-control--has-help-text': hasHelpText,
383
+ 'form-control--has-label': hasLabel,
356
384
  'form-control--large': this.size === 'large',
385
+ 'form-control--medium': this.size === 'medium',
357
386
  'form-control--radio-group': true,
358
- 'form-control--has-label': hasLabel,
359
- 'form-control--has-help-text': hasHelpText
387
+ 'form-control--small': this.size === 'small',
360
388
  })}
361
389
  role="radiogroup"
362
390
  aria-labelledby="label"
@@ -407,5 +435,6 @@ export default class SynRadioGroup extends SynergyElement implements SynergyForm
407
435
  </div>
408
436
  </fieldset>
409
437
  `;
438
+ /* eslint-enable @typescript-eslint/unbound-method */
410
439
  }
411
440
  }
@@ -177,6 +177,19 @@ export class SynRangeComponent {
177
177
  return this.nativeElement.disabled;
178
178
  }
179
179
 
180
+ /**
181
+ * Sets the range to a readonly state.
182
+ */
183
+ @Input()
184
+ set readonly(v: '' | SynRange['readonly']) {
185
+ this._ngZone.runOutsideAngular(
186
+ () => (this.nativeElement.readonly = v === '' || v),
187
+ );
188
+ }
189
+ get readonly(): SynRange['readonly'] {
190
+ return this.nativeElement.readonly;
191
+ }
192
+
180
193
  /**
181
194
  * The minimum acceptable value of the range.
182
195
  */
@@ -213,20 +213,26 @@ export default css`
213
213
  cursor: not-allowed;
214
214
  }
215
215
 
216
+ :host([readonly]) .track__wrapper,
217
+ :host([readonly]) .thumb,
218
+ :host([readonly]) .thumb.grabbed {
219
+ cursor: default;
220
+ }
221
+
216
222
  /*
217
223
  * Guard against mobile devices not removing the transform
218
224
  * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover
219
225
  */
220
226
  @media (any-hover: hover) {
221
- :host(:not([disabled])) .thumb:hover {
227
+ :host(:not([disabled]):not([readonly])) .thumb:hover {
222
228
  transform: scale(var(--thumb-hit-area-size));
223
229
  }
224
230
 
225
- :host(:not([disabled])) .thumb:not(.grabbed):hover {
231
+ :host(:not([disabled]):not([readonly])) .thumb:not(.grabbed):hover {
226
232
  background: var(--syn-interactive-emphasis-color-hover);
227
233
  }
228
234
 
229
- :host(:not([disabled])) .thumb:hover::after {
235
+ :host(:not([disabled]):not([readonly])) .thumb:hover::after {
230
236
  /* Unset the area of the thumb click and drag area space, so it does not scale with the hover */
231
237
  inset: unset;
232
238
  }
@@ -282,4 +288,22 @@ export default css`
282
288
  :host([data-user-invalid]) .thumb {
283
289
  background-color: var(--syn-range-error-color);
284
290
  }
291
+
292
+ /**
293
+ * #1176: Readonly state
294
+ */
295
+ :host([readonly]) {
296
+ --track-color-active: var(--syn-readonly-indicator-color);
297
+ --track-color-inactive: var(--syn-readonly-background-color);
298
+ }
299
+
300
+ :host([readonly]) .thumb {
301
+ background-color: var(--syn-readonly-indicator-color);
302
+ }
303
+
304
+ :host([readonly]) .thumb:focus {
305
+ background-color: var(--syn-readonly-indicator-color);
306
+ outline: var(--syn-focus-ring);
307
+ outline-offset: 0;
308
+ }
285
309
  `;