@synergy-design-system/mcp 2.6.1 → 2.7.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 (64) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/utilities/storybook/scraper.js +14 -3
  3. package/metadata/checksum.txt +1 -1
  4. package/metadata/packages/components/components/syn-checkbox/component.angular.ts +13 -0
  5. package/metadata/packages/components/components/syn-checkbox/component.styles.ts +99 -39
  6. package/metadata/packages/components/components/syn-checkbox/component.ts +13 -10
  7. package/metadata/packages/components/components/syn-checkbox/component.vue +5 -0
  8. package/metadata/packages/components/components/syn-combobox/component.angular.ts +13 -0
  9. package/metadata/packages/components/components/syn-combobox/component.styles.ts +216 -193
  10. package/metadata/packages/components/components/syn-combobox/component.ts +68 -39
  11. package/metadata/packages/components/components/syn-combobox/component.vue +5 -0
  12. package/metadata/packages/components/components/syn-file/component.angular.ts +13 -0
  13. package/metadata/packages/components/components/syn-file/component.styles.ts +20 -3
  14. package/metadata/packages/components/components/syn-file/component.ts +19 -5
  15. package/metadata/packages/components/components/syn-file/component.vue +5 -0
  16. package/metadata/packages/components/components/syn-input/component.ts +1 -2
  17. package/metadata/packages/components/components/syn-radio/component.angular.ts +13 -0
  18. package/metadata/packages/components/components/syn-radio/component.styles.ts +91 -29
  19. package/metadata/packages/components/components/syn-radio/component.ts +19 -10
  20. package/metadata/packages/components/components/syn-radio/component.vue +5 -0
  21. package/metadata/packages/components/components/syn-radio-group/component.styles.ts +30 -9
  22. package/metadata/packages/components/components/syn-radio-group/component.ts +61 -32
  23. package/metadata/packages/components/components/syn-range/component.angular.ts +13 -0
  24. package/metadata/packages/components/components/syn-range/component.styles.ts +27 -3
  25. package/metadata/packages/components/components/syn-range/component.ts +17 -5
  26. package/metadata/packages/components/components/syn-range/component.vue +5 -0
  27. package/metadata/packages/components/components/syn-select/component.angular.ts +13 -0
  28. package/metadata/packages/components/components/syn-select/component.styles.ts +222 -151
  29. package/metadata/packages/components/components/syn-select/component.ts +30 -15
  30. package/metadata/packages/components/components/syn-select/component.vue +5 -0
  31. package/metadata/packages/components/components/syn-switch/component.angular.ts +13 -0
  32. package/metadata/packages/components/components/syn-switch/component.styles.ts +145 -63
  33. package/metadata/packages/components/components/syn-switch/component.ts +16 -4
  34. package/metadata/packages/components/components/syn-switch/component.vue +5 -0
  35. package/metadata/packages/components/components/syn-textarea/component.styles.ts +55 -27
  36. package/metadata/packages/components/components/syn-textarea/component.ts +1 -3
  37. package/metadata/packages/components/static/CHANGELOG.md +27 -0
  38. package/metadata/packages/tokens/CHANGELOG.md +22 -0
  39. package/metadata/packages/tokens/dark.css +7 -1
  40. package/metadata/packages/tokens/index.js +31 -1
  41. package/metadata/packages/tokens/light.css +7 -1
  42. package/metadata/packages/tokens/sick2018_dark.css +7 -1
  43. package/metadata/packages/tokens/sick2018_light.css +7 -1
  44. package/metadata/packages/tokens/sick2025_dark.css +7 -1
  45. package/metadata/packages/tokens/sick2025_light.css +7 -1
  46. package/metadata/static/components/syn-checkbox/docs.md +36 -0
  47. package/metadata/static/components/syn-combobox/docs.md +138 -0
  48. package/metadata/static/components/syn-file/docs.md +24 -0
  49. package/metadata/static/components/syn-input/docs.md +1 -1
  50. package/metadata/static/components/syn-radio/docs.md +21 -0
  51. package/metadata/static/components/syn-radio-group/docs.md +46 -0
  52. package/metadata/static/components/syn-range/docs.md +19 -0
  53. package/metadata/static/components/syn-select/docs.md +81 -0
  54. package/metadata/static/components/syn-switch/docs.md +22 -0
  55. package/metadata/static/components/syn-textarea/docs.md +1 -1
  56. package/metadata/static/components/syn-tooltip/docs.md +73 -0
  57. package/package.json +4 -4
  58. package/metadata/packages/components/components/syn-checkbox/component.custom.styles.ts +0 -86
  59. package/metadata/packages/components/components/syn-combobox/component.custom.styles.ts +0 -122
  60. package/metadata/packages/components/components/syn-radio/component.custom.styles.ts +0 -86
  61. package/metadata/packages/components/components/syn-radio-group/component.custom.styles.ts +0 -25
  62. package/metadata/packages/components/components/syn-select/component.custom.styles.ts +0 -175
  63. package/metadata/packages/components/components/syn-switch/component.custom.styles.ts +0 -141
  64. package/metadata/packages/components/components/syn-textarea/component.custom.styles.ts +0 -48
@@ -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
  `;
@@ -11,7 +11,6 @@ import { HasSlotController } from '../../internal/slot.js';
11
11
  import { LocalizeController } from '../../utilities/localize.js';
12
12
  import componentStyles from '../../styles/component.styles.js';
13
13
  import formControlStyles from '../../styles/form-control.styles.js';
14
- import formControlCustomStyles from '../../styles/form-control.custom.styles.js';
15
14
  import SynergyElement from '../../internal/synergy-element.js';
16
15
  import SynTooltip from '../tooltip/tooltip.component.js';
17
16
  import {
@@ -78,7 +77,6 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
78
77
  static styles: CSSResultGroup = [
79
78
  componentStyles,
80
79
  formControlStyles,
81
- formControlCustomStyles,
82
80
  styles,
83
81
  ];
84
82
 
@@ -98,6 +96,9 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
98
96
  /** Disables the range. */
99
97
  @property({ reflect: true, type: Boolean }) disabled = false;
100
98
 
99
+ /** Sets the range to a readonly state. */
100
+ @property({ reflect: true, type: Boolean }) readonly = false;
101
+
101
102
  /** The minimum acceptable value of the range. */
102
103
  @property({ type: Number }) min = 0;
103
104
 
@@ -354,6 +355,12 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
354
355
 
355
356
  #onClickTrack(event: PointerEvent, focusThumb = true) {
356
357
  if (this.disabled) return;
358
+ if (this.readonly) {
359
+ event.preventDefault();
360
+ this.focus();
361
+ return;
362
+ }
363
+
357
364
  const { clientX } = event;
358
365
 
359
366
  const thumbs = Array.from(this.thumbs);
@@ -443,7 +450,7 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
443
450
  }
444
451
 
445
452
  async #onClickThumb(event: PointerEvent) {
446
- if (this.disabled) return;
453
+ if (this.disabled || this.readonly) return;
447
454
 
448
455
  const thumb = event.target as HTMLDivElement;
449
456
  this.#updateTooltip(thumb);
@@ -460,7 +467,7 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
460
467
  }
461
468
 
462
469
  #onDragThumb(event: PointerEvent) {
463
- if (this.disabled) return;
470
+ if (this.disabled || this.readonly) return;
464
471
 
465
472
  const thumb = event.target as HTMLDivElement;
466
473
  const rangeId = +thumb.dataset.rangeId!;
@@ -509,6 +516,8 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
509
516
  }
510
517
 
511
518
  async #onReleaseThumb(event: PointerEvent) {
519
+ if (this.disabled || this.readonly) return;
520
+
512
521
  const thumb = event.target as HTMLDivElement;
513
522
  if (!thumb.dataset.pointerId || event.pointerId !== +thumb.dataset.pointerId) return;
514
523
 
@@ -569,6 +578,8 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
569
578
  }
570
579
 
571
580
  #onKeyPress(event: KeyboardEvent) {
581
+ if (this.readonly) return;
582
+
572
583
  const thumb = event.target as HTMLDivElement;
573
584
  const rangeId = +thumb.dataset.rangeId!;
574
585
 
@@ -756,7 +767,7 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
756
767
  trigger="focus"
757
768
  >
758
769
  <div
759
- aria-disabled=${ifDefined(this.disabled ? 'true' : undefined)}
770
+ aria-disabled=${ifDefined((this.disabled || this.readonly) ? 'true' : undefined)}
760
771
  aria-labelledby=${ariaLabeledBy}
761
772
  aria-label=${ariaLabel}
762
773
  aria-valuemax="${this.max}"
@@ -802,6 +813,7 @@ export default class SynRange extends SynergyElement implements SynergyFormContr
802
813
  'form-control--has-prefix': hasPrefixSlot,
803
814
  'form-control--has-suffix': hasSuffixSlot,
804
815
  'form-control--is-disabled': this.disabled,
816
+ 'form-control--is-readonly': this.readonly,
805
817
  'form-control--large': this.size === 'large',
806
818
  'form-control--medium': this.size === 'medium',
807
819
  'form-control--small': this.size === 'small',
@@ -98,6 +98,11 @@ const props = defineProps<{
98
98
  */
99
99
  disabled?: SynRange['disabled'];
100
100
 
101
+ /**
102
+ * Sets the range to a readonly state.
103
+ */
104
+ readonly?: SynRange['readonly'];
105
+
101
106
  /**
102
107
  * The minimum acceptable value of the range.
103
108
  */
@@ -235,6 +235,19 @@ indicate the number of additional items that are selected.
235
235
  return this.nativeElement.disabled;
236
236
  }
237
237
 
238
+ /**
239
+ * Sets the select to a readonly state.
240
+ */
241
+ @Input()
242
+ set readonly(v: '' | SynSelect['readonly']) {
243
+ this._ngZone.runOutsideAngular(
244
+ () => (this.nativeElement.readonly = v === '' || v),
245
+ );
246
+ }
247
+ get readonly(): SynSelect['readonly'] {
248
+ return this.nativeElement.readonly;
249
+ }
250
+
238
251
  /**
239
252
  * Adds a clear button when the select is not empty.
240
253
  */