@vaadin/radio-group 24.2.3 → 24.3.0-alpha10

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.
@@ -3,18 +3,15 @@
3
3
  * Copyright (c) 2017 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import './vaadin-radio-button.js';
6
7
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
7
- import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
8
- import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
9
- import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
10
8
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
11
9
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
12
- import { SlotObserver } from '@vaadin/component-base/src/slot-observer.js';
13
- import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
14
- import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
15
- import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
16
- import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
17
- import { RadioButton } from './vaadin-radio-button.js';
10
+ import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
11
+ import { RadioGroupMixin } from './vaadin-radio-group-mixin.js';
12
+ import { radioGroupStyles } from './vaadin-radio-group-styles.js';
13
+
14
+ registerStyles('vaadin-radio-group', radioGroupStyles, { moduleId: 'vaadin-radio-group-styles' });
18
15
 
19
16
  /**
20
17
  * `<vaadin-radio-group>` is a web component that allows the user to choose one item from a group of choices.
@@ -61,51 +58,16 @@ import { RadioButton } from './vaadin-radio-button.js';
61
58
  * @customElement
62
59
  * @extends HTMLElement
63
60
  * @mixes ThemableMixin
64
- * @mixes DisabledMixin
65
61
  * @mixes ElementMixin
66
- * @mixes FocusMixin
67
- * @mixes FieldMixin
68
- * @mixes KeyboardMixin
62
+ * @mixes RadioGroupMixin
69
63
  */
70
- class RadioGroup extends FieldMixin(
71
- FocusMixin(DisabledMixin(KeyboardMixin(ElementMixin(ThemableMixin(PolymerElement))))),
72
- ) {
64
+ class RadioGroup extends RadioGroupMixin(ElementMixin(ThemableMixin(PolymerElement))) {
73
65
  static get is() {
74
66
  return 'vaadin-radio-group';
75
67
  }
76
68
 
77
69
  static get template() {
78
70
  return html`
79
- <style>
80
- :host {
81
- display: inline-flex;
82
- }
83
-
84
- :host::before {
85
- content: '\\2003';
86
- width: 0;
87
- display: inline-block;
88
- }
89
-
90
- :host([hidden]) {
91
- display: none !important;
92
- }
93
-
94
- .vaadin-group-field-container {
95
- display: flex;
96
- flex-direction: column;
97
- width: 100%;
98
- }
99
-
100
- [part='group-field'] {
101
- display: flex;
102
- flex-wrap: wrap;
103
- }
104
-
105
- :host(:not([has-label])) [part='label'] {
106
- display: none;
107
- }
108
- </style>
109
71
  <div class="vaadin-group-field-container">
110
72
  <div part="label">
111
73
  <slot name="label"></slot>
@@ -128,404 +90,6 @@ class RadioGroup extends FieldMixin(
128
90
  <slot name="tooltip"></slot>
129
91
  `;
130
92
  }
131
-
132
- static get properties() {
133
- return {
134
- /**
135
- * The value of the radio group.
136
- *
137
- * @type {string}
138
- */
139
- value: {
140
- type: String,
141
- notify: true,
142
- value: '',
143
- observer: '__valueChanged',
144
- },
145
-
146
- /**
147
- * When present, the user cannot modify the value of the radio group.
148
- * The property works similarly to the `disabled` property.
149
- * While the `disabled` property disables all radio buttons inside the group,
150
- * the `readonly` property disables only unchecked ones.
151
- *
152
- * @type {boolean}
153
- */
154
- readonly: {
155
- type: Boolean,
156
- value: false,
157
- reflectToAttribute: true,
158
- observer: '__readonlyChanged',
159
- },
160
-
161
- /**
162
- * @type {string}
163
- * @private
164
- */
165
- _fieldName: {
166
- type: String,
167
- },
168
- };
169
- }
170
-
171
- constructor() {
172
- super();
173
-
174
- this.__registerRadioButton = this.__registerRadioButton.bind(this);
175
- this.__unregisterRadioButton = this.__unregisterRadioButton.bind(this);
176
- this.__onRadioButtonCheckedChange = this.__onRadioButtonCheckedChange.bind(this);
177
-
178
- this._tooltipController = new TooltipController(this);
179
- this._tooltipController.addEventListener('tooltip-changed', (event) => {
180
- const tooltip = event.detail.node;
181
- if (tooltip && tooltip.isConnected) {
182
- // Tooltip element has been added to the DOM
183
- const inputs = this.__radioButtons.map((radio) => radio.inputElement);
184
- this._tooltipController.setAriaTarget(inputs);
185
- } else {
186
- // Tooltip element is no longer connected
187
- this._tooltipController.setAriaTarget([]);
188
- }
189
- });
190
- }
191
-
192
- /**
193
- * A collection of the group's radio buttons.
194
- *
195
- * @return {!Array<!RadioButton>}
196
- * @private
197
- */
198
- get __radioButtons() {
199
- return this.__filterRadioButtons([...this.children]);
200
- }
201
-
202
- /**
203
- * A currently selected radio button.
204
- *
205
- * @return {!RadioButton | undefined}
206
- * @private
207
- */
208
- get __selectedRadioButton() {
209
- return this.__radioButtons.find((radioButton) => radioButton.checked);
210
- }
211
-
212
- /**
213
- * @return {boolean}
214
- * @private
215
- */
216
- get isHorizontalRTL() {
217
- return this.__isRTL && this._theme !== 'vertical';
218
- }
219
-
220
- /** @protected */
221
- ready() {
222
- super.ready();
223
-
224
- this.ariaTarget = this;
225
-
226
- // See https://github.com/vaadin/vaadin-web-components/issues/94
227
- this.setAttribute('role', 'radiogroup');
228
-
229
- this._fieldName = `${this.localName}-${generateUniqueId()}`;
230
-
231
- const slot = this.shadowRoot.querySelector('slot:not([name])');
232
- this._observer = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
233
- // Registers the added radio buttons in the reverse order
234
- // in order for the group to take the value of the most recent button.
235
- this.__filterRadioButtons(addedNodes).reverse().forEach(this.__registerRadioButton);
236
-
237
- // Unregisters the removed radio buttons.
238
- this.__filterRadioButtons(removedNodes).forEach(this.__unregisterRadioButton);
239
-
240
- const inputs = this.__radioButtons.map((radio) => radio.inputElement);
241
- this._tooltipController.setAriaTarget(inputs);
242
- });
243
-
244
- this.addController(this._tooltipController);
245
- }
246
-
247
- /**
248
- * @param {!Array<!Node>} nodes
249
- * @return {!Array<!RadioButton>}
250
- * @private
251
- */
252
- __filterRadioButtons(nodes) {
253
- return nodes.filter((child) => child instanceof RadioButton);
254
- }
255
-
256
- /**
257
- * Override method inherited from `KeyboardMixin`
258
- * to implement the custom keyboard navigation as a replacement for the native one
259
- * in order for the navigation to work the same way across different browsers.
260
- *
261
- * @param {!KeyboardEvent} event
262
- * @override
263
- * @protected
264
- */
265
- _onKeyDown(event) {
266
- super._onKeyDown(event);
267
-
268
- const radioButton = event.composedPath().find((node) => node instanceof RadioButton);
269
-
270
- if (['ArrowLeft', 'ArrowUp'].includes(event.key)) {
271
- event.preventDefault();
272
- this.__selectNextRadioButton(radioButton);
273
- }
274
-
275
- if (['ArrowRight', 'ArrowDown'].includes(event.key)) {
276
- event.preventDefault();
277
- this.__selectPrevRadioButton(radioButton);
278
- }
279
- }
280
-
281
- /**
282
- * Override an observer from `FieldMixin`.
283
- *
284
- * @param {boolean} invalid
285
- * @protected
286
- * @override
287
- */
288
- _invalidChanged(invalid) {
289
- super._invalidChanged(invalid);
290
-
291
- if (invalid) {
292
- this.setAttribute('aria-invalid', 'true');
293
- } else {
294
- this.removeAttribute('aria-invalid');
295
- }
296
- }
297
-
298
- /**
299
- * @param {number} index
300
- * @private
301
- */
302
- __selectNextRadioButton(radioButton) {
303
- const index = this.__radioButtons.indexOf(radioButton);
304
-
305
- this.__selectIncRadioButton(index, this.isHorizontalRTL ? 1 : -1);
306
- }
307
-
308
- /**
309
- * @param {number} index
310
- * @private
311
- */
312
- __selectPrevRadioButton(radioButton) {
313
- const index = this.__radioButtons.indexOf(radioButton);
314
-
315
- this.__selectIncRadioButton(index, this.isHorizontalRTL ? -1 : 1);
316
- }
317
-
318
- /**
319
- * @param {number} index
320
- * @param {number} step
321
- * @private
322
- */
323
- __selectIncRadioButton(index, step) {
324
- const newIndex = (this.__radioButtons.length + index + step) % this.__radioButtons.length;
325
- const newRadioButton = this.__radioButtons[newIndex];
326
-
327
- if (newRadioButton.disabled) {
328
- this.__selectIncRadioButton(newIndex, step);
329
- } else {
330
- newRadioButton.focusElement.focus();
331
- newRadioButton.focusElement.click();
332
- }
333
- }
334
-
335
- /**
336
- * Registers the radio button after adding it to the group.
337
- *
338
- * @param {!RadioButton} radioButton
339
- * @private
340
- */
341
- __registerRadioButton(radioButton) {
342
- radioButton.name = this._fieldName;
343
- radioButton.addEventListener('checked-changed', this.__onRadioButtonCheckedChange);
344
-
345
- if (this.disabled || this.readonly) {
346
- radioButton.disabled = true;
347
- }
348
-
349
- if (radioButton.checked) {
350
- this.__selectRadioButton(radioButton);
351
- }
352
- }
353
-
354
- /**
355
- * Unregisters the radio button before removing it from the group.
356
- *
357
- * @param {!RadioButton} radioButton
358
- * @private
359
- */
360
- __unregisterRadioButton(radioButton) {
361
- radioButton.removeEventListener('checked-changed', this.__onRadioButtonCheckedChange);
362
-
363
- if (radioButton.value === this.value) {
364
- this.__selectRadioButton(null);
365
- }
366
- }
367
-
368
- /**
369
- * @param {!CustomEvent} event
370
- * @private
371
- */
372
- __onRadioButtonCheckedChange(event) {
373
- if (event.target.checked) {
374
- this.__selectRadioButton(event.target);
375
- }
376
- }
377
-
378
- /**
379
- * Whenever the user sets a non-empty value,
380
- * the method tries to select the radio button with that value
381
- * showing a warning if no radio button was found with the given value.
382
- * If the new value is empty, the method deselects the currently selected radio button.
383
- * At last, the method toggles the `has-value` attribute considering the new value.
384
- *
385
- * @param {string | null | undefined} newValue
386
- * @param {string | null | undefined} oldValue
387
- * @private
388
- */
389
- __valueChanged(newValue, oldValue) {
390
- if (oldValue === undefined && newValue === '') {
391
- return;
392
- }
393
-
394
- if (newValue) {
395
- const newSelectedRadioButton = this.__radioButtons.find((radioButton) => {
396
- return radioButton.value === newValue;
397
- });
398
-
399
- if (newSelectedRadioButton) {
400
- this.__selectRadioButton(newSelectedRadioButton);
401
- this.toggleAttribute('has-value', true);
402
- } else {
403
- console.warn(`The radio button with the value "${newValue}" was not found.`);
404
- }
405
- } else {
406
- this.__selectRadioButton(null);
407
- this.removeAttribute('has-value');
408
- }
409
-
410
- if (oldValue !== undefined) {
411
- this.validate();
412
- }
413
- }
414
-
415
- /**
416
- * Whenever `readonly` property changes on the group element,
417
- * the method updates the `disabled` property for the radio buttons.
418
- *
419
- * @param {boolean} newValue
420
- * @param {boolean} oldValue
421
- * @private
422
- */
423
- __readonlyChanged(newValue, oldValue) {
424
- // Prevent updating the `disabled` property for the radio buttons at initialization.
425
- // Otherwise, the group's radio buttons may end up enabled regardless
426
- // an intentionally added `disabled` attribute on some of them.
427
- if (!newValue && oldValue === undefined) {
428
- return;
429
- }
430
-
431
- if (oldValue !== newValue) {
432
- this.__updateRadioButtonsDisabledProperty();
433
- }
434
- }
435
-
436
- /**
437
- * Override method inherited from `DisabledMixin`
438
- * to update the `disabled` property for the radio buttons
439
- * whenever the property changes on the group element.
440
- *
441
- * @param {boolean} newValue
442
- * @param {boolean} oldValue
443
- * @override
444
- * @protected
445
- */
446
- _disabledChanged(newValue, oldValue) {
447
- super._disabledChanged(newValue, oldValue);
448
-
449
- // Prevent updating the `disabled` property for the radio buttons at initialization.
450
- // Otherwise, the group's radio buttons may end up enabled regardless
451
- // an intentionally added `disabled` attribute on some of them.
452
- if (!newValue && oldValue === undefined) {
453
- return;
454
- }
455
-
456
- if (oldValue !== newValue) {
457
- this.__updateRadioButtonsDisabledProperty();
458
- }
459
- }
460
-
461
- /**
462
- * Override method inherited from `FocusMixin`
463
- * to prevent removing the `focused` attribute
464
- * when focus moves between radio buttons inside the group.
465
- *
466
- * @param {!FocusEvent} event
467
- * @return {boolean}
468
- * @protected
469
- */
470
- _shouldRemoveFocus(event) {
471
- return !this.contains(event.relatedTarget);
472
- }
473
-
474
- /**
475
- * Override method inherited from `FocusMixin`
476
- * to run validation when the group loses focus.
477
- *
478
- * @param {boolean} focused
479
- * @override
480
- * @protected
481
- */
482
- _setFocused(focused) {
483
- super._setFocused(focused);
484
-
485
- // Do not validate when focusout is caused by document
486
- // losing focus, which happens on browser tab switch.
487
- if (!focused && document.hasFocus()) {
488
- this.validate();
489
- }
490
- }
491
-
492
- /**
493
- * @param {RadioButton} radioButton
494
- * @private
495
- */
496
- __selectRadioButton(radioButton) {
497
- if (radioButton) {
498
- this.value = radioButton.value;
499
- } else {
500
- this.value = '';
501
- }
502
-
503
- this.__radioButtons.forEach((button) => {
504
- button.checked = button === radioButton;
505
- });
506
-
507
- if (this.readonly) {
508
- this.__updateRadioButtonsDisabledProperty();
509
- }
510
- }
511
-
512
- /**
513
- * If the group is read-only, the method disables the unchecked radio buttons.
514
- * Otherwise, the method propagates the group's `disabled` property to the radio buttons.
515
- *
516
- * @private
517
- */
518
- __updateRadioButtonsDisabledProperty() {
519
- this.__radioButtons.forEach((button) => {
520
- if (this.readonly) {
521
- // The native radio button doesn't support the `readonly` attribute
522
- // so the state can be only imitated, by disabling unchecked radio buttons.
523
- button.disabled = button !== this.__selectedRadioButton;
524
- } else {
525
- button.disabled = this.disabled;
526
- }
527
- });
528
- }
529
93
  }
530
94
 
531
95
  defineCustomElement(RadioGroup);
@@ -9,8 +9,8 @@ registerStyles(
9
9
  'vaadin-radio-button',
10
10
  css`
11
11
  :host {
12
- color: var(--lumo-body-text-color);
13
- font-size: var(--lumo-font-size-m);
12
+ color: var(--vaadin-radio-button-label-color, var(--lumo-body-text-color));
13
+ font-size: var(--vaadin-radio-button-label-font-size, var(--lumo-font-size-m));
14
14
  font-family: var(--lumo-font-family);
15
15
  line-height: var(--lumo-line-height-s);
16
16
  -webkit-font-smoothing: antialiased;
@@ -22,11 +22,16 @@ registerStyles(
22
22
  cursor: default;
23
23
  outline: none;
24
24
  --_radio-button-size: var(--vaadin-radio-button-size, calc(var(--lumo-size-m) / 2));
25
+ --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct));
26
+ --_focus-ring-width: var(--vaadin-focus-ring-width, 2px);
27
+ --_selection-color: var(--vaadin-selection-color, var(--lumo-primary-color));
25
28
  }
26
29
 
27
30
  :host([has-label]) ::slotted(label) {
28
- padding-block: var(--lumo-space-xs);
29
- padding-inline: var(--lumo-space-xs) var(--lumo-space-s);
31
+ padding: var(
32
+ --vaadin-radio-button-label-padding,
33
+ var(--lumo-space-xs) var(--lumo-space-s) var(--lumo-space-xs) var(--lumo-space-xs)
34
+ );
30
35
  }
31
36
 
32
37
  [part='radio'] {
@@ -35,7 +40,7 @@ registerStyles(
35
40
  margin: var(--lumo-space-xs);
36
41
  position: relative;
37
42
  border-radius: 50%;
38
- background-color: var(--lumo-contrast-20pct);
43
+ background: var(--vaadin-radio-button-background, var(--lumo-contrast-20pct));
39
44
  transition: transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), background-color 0.15s;
40
45
  will-change: transform;
41
46
  cursor: var(--lumo-clickable-cursor);
@@ -64,7 +69,8 @@ registerStyles(
64
69
  pointer-events: none;
65
70
  width: 0;
66
71
  height: 0;
67
- border: 3px solid var(--lumo-primary-contrast-color);
72
+ border: var(--vaadin-radio-button-dot-size, 3px) solid
73
+ var(--vaadin-radio-button-dot-color, var(--lumo-primary-contrast-color));
68
74
  border-radius: 50%;
69
75
  position: absolute;
70
76
  top: 50%;
@@ -80,7 +86,7 @@ registerStyles(
80
86
  }
81
87
 
82
88
  :host([checked]) [part='radio'] {
83
- background-color: var(--lumo-primary-color);
89
+ background-color: var(--_selection-color);
84
90
  }
85
91
 
86
92
  :host([checked]) [part='radio']::after {
@@ -88,7 +94,7 @@ registerStyles(
88
94
  }
89
95
 
90
96
  :host(:not([checked]):not([disabled]):hover) [part='radio'] {
91
- background-color: var(--lumo-contrast-30pct);
97
+ background: var(--vaadin-radio-button-background-hover, var(--lumo-contrast-30pct));
92
98
  }
93
99
 
94
100
  :host([active]) [part='radio'] {
@@ -107,7 +113,7 @@ registerStyles(
107
113
  }
108
114
 
109
115
  :host([focus-ring]) [part='radio'] {
110
- box-shadow: 0 0 0 1px var(--lumo-base-color), 0 0 0 3px var(--lumo-primary-color-50pct),
116
+ box-shadow: 0 0 0 1px var(--lumo-base-color), 0 0 0 calc(var(--_focus-ring-width) + 1px) var(--_focus-ring-color),
111
117
  inset 0 0 0 var(--_input-border-width, 0) var(--_input-border-color);
112
118
  }
113
119
 
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/radio-group",
4
- "version": "24.2.3",
4
+ "version": "24.3.0-alpha10",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -41,6 +41,17 @@
41
41
  ]
42
42
  }
43
43
  },
44
+ {
45
+ "name": "autofocus",
46
+ "description": "Specify that this control should have input focus when the page loads.",
47
+ "value": {
48
+ "type": [
49
+ "boolean",
50
+ "null",
51
+ "undefined"
52
+ ]
53
+ }
54
+ },
44
55
  {
45
56
  "name": "label",
46
57
  "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
@@ -106,6 +117,17 @@
106
117
  ]
107
118
  }
108
119
  },
120
+ {
121
+ "name": "autofocus",
122
+ "description": "Specify that this control should have input focus when the page loads.",
123
+ "value": {
124
+ "type": [
125
+ "boolean",
126
+ "null",
127
+ "undefined"
128
+ ]
129
+ }
130
+ },
109
131
  {
110
132
  "name": "label",
111
133
  "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
@@ -375,13 +397,13 @@
375
397
  "name": "validated",
376
398
  "description": "Fired whenever the field is validated."
377
399
  },
378
- {
379
- "name": "value-changed",
380
- "description": "Fired when the `value` property changes."
381
- },
382
400
  {
383
401
  "name": "invalid-changed",
384
402
  "description": "Fired when the `invalid` property changes."
403
+ },
404
+ {
405
+ "name": "value-changed",
406
+ "description": "Fired when the `value` property changes."
385
407
  }
386
408
  ]
387
409
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/radio-group",
4
- "version": "24.2.3",
4
+ "version": "24.3.0-alpha10",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -33,6 +33,13 @@
33
33
  "kind": "expression"
34
34
  }
35
35
  },
36
+ {
37
+ "name": "?autofocus",
38
+ "description": "Specify that this control should have input focus when the page loads.",
39
+ "value": {
40
+ "kind": "expression"
41
+ }
42
+ },
36
43
  {
37
44
  "name": ".value",
38
45
  "description": "The value of the field.",
@@ -153,15 +160,15 @@
153
160
  }
154
161
  },
155
162
  {
156
- "name": "@value-changed",
157
- "description": "Fired when the `value` property changes.",
163
+ "name": "@invalid-changed",
164
+ "description": "Fired when the `invalid` property changes.",
158
165
  "value": {
159
166
  "kind": "expression"
160
167
  }
161
168
  },
162
169
  {
163
- "name": "@invalid-changed",
164
- "description": "Fired when the `invalid` property changes.",
170
+ "name": "@value-changed",
171
+ "description": "Fired when the `value` property changes.",
165
172
  "value": {
166
173
  "kind": "expression"
167
174
  }