@vaadin/checkbox-group 22.0.0-alpha6 → 22.0.0-alpha7

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.
package/package.json CHANGED
@@ -1,10 +1,29 @@
1
1
  {
2
2
  "name": "@vaadin/checkbox-group",
3
- "version": "22.0.0-alpha6",
3
+ "version": "22.0.0-alpha7",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
4
7
  "description": "vaadin-checkbox-group",
8
+ "license": "Apache-2.0",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/vaadin/web-components.git",
12
+ "directory": "packages/checkbox-group"
13
+ },
14
+ "author": "Vaadin Ltd",
15
+ "homepage": "https://vaadin.com/components",
16
+ "bugs": {
17
+ "url": "https://github.com/vaadin/web-components/issues"
18
+ },
5
19
  "main": "vaadin-checkbox-group.js",
6
20
  "module": "vaadin-checkbox-group.js",
7
- "repository": "vaadin/web-components",
21
+ "files": [
22
+ "src",
23
+ "theme",
24
+ "vaadin-*.d.ts",
25
+ "vaadin-*.js"
26
+ ],
8
27
  "keywords": [
9
28
  "Vaadin",
10
29
  "checkbox",
@@ -12,33 +31,18 @@
12
31
  "web-component",
13
32
  "polymer"
14
33
  ],
15
- "author": "Vaadin Ltd",
16
- "license": "Apache-2.0",
17
- "bugs": {
18
- "url": "https://github.com/vaadin/web-components/issues"
19
- },
20
- "homepage": "https://vaadin.com/components",
21
- "files": [
22
- "vaadin-*.d.ts",
23
- "vaadin-*.js",
24
- "src",
25
- "theme"
26
- ],
27
34
  "dependencies": {
28
35
  "@polymer/polymer": "^3.0.0",
29
- "@vaadin/checkbox": "^22.0.0-alpha6",
30
- "@vaadin/vaadin-element-mixin": "^22.0.0-alpha6",
31
- "@vaadin/vaadin-lumo-styles": "^22.0.0-alpha6",
32
- "@vaadin/vaadin-material-styles": "^22.0.0-alpha6",
33
- "@vaadin/vaadin-themable-mixin": "^22.0.0-alpha6"
36
+ "@vaadin/checkbox": "22.0.0-alpha7",
37
+ "@vaadin/component-base": "22.0.0-alpha7",
38
+ "@vaadin/vaadin-lumo-styles": "22.0.0-alpha7",
39
+ "@vaadin/vaadin-material-styles": "22.0.0-alpha7",
40
+ "@vaadin/vaadin-themable-mixin": "22.0.0-alpha7"
34
41
  },
35
42
  "devDependencies": {
36
43
  "@esm-bundle/chai": "^4.3.4",
37
44
  "@vaadin/testing-helpers": "^0.3.0",
38
45
  "sinon": "^9.2.0"
39
46
  },
40
- "publishConfig": {
41
- "access": "public"
42
- },
43
- "gitHead": "4b136b1c7da8942960e7255f40c27859125b3a45"
47
+ "gitHead": "8e89419c6b44a1d225d5859e180d7b35e47ddb52"
44
48
  }
@@ -1,9 +1,14 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
7
+ import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js';
8
+ import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
9
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
1
10
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
2
11
 
3
- import { DirMixin } from '@vaadin/vaadin-element-mixin/vaadin-dir-mixin.js';
4
-
5
- import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js';
6
-
7
12
  /**
8
13
  * Fired when the `invalid` property changes.
9
14
  */
@@ -14,16 +19,16 @@ export type CheckboxGroupInvalidChangedEvent = CustomEvent<{ value: boolean }>;
14
19
  */
15
20
  export type CheckboxGroupValueChangedEvent = CustomEvent<{ value: Array<string> }>;
16
21
 
17
- export interface CheckboxGroupElementEventMap {
22
+ export interface CheckboxGroupCustomEventMap {
18
23
  'invalid-changed': CheckboxGroupInvalidChangedEvent;
19
24
 
20
25
  'value-changed': CheckboxGroupValueChangedEvent;
21
26
  }
22
27
 
23
- export interface CheckboxGroupEventMap extends HTMLElementEventMap, CheckboxGroupElementEventMap {}
28
+ export interface CheckboxGroupEventMap extends HTMLElementEventMap, CheckboxGroupCustomEventMap {}
24
29
 
25
30
  /**
26
- * `<vaadin-checkbox-group>` is a Polymer element for grouping vaadin-checkboxes.
31
+ * `<vaadin-checkbox-group>` is a web component that allows the user to choose several items from a group of binary choices.
27
32
  *
28
33
  * ```html
29
34
  * <vaadin-checkbox-group label="Preferred language of contact:">
@@ -37,105 +42,57 @@ export interface CheckboxGroupEventMap extends HTMLElementEventMap, CheckboxGrou
37
42
  *
38
43
  * The following shadow DOM parts are available for styling:
39
44
  *
40
- * Part name | Description
41
- * ----------------|----------------
42
- * `label` | The label element
43
- * `group-field` | The element that wraps checkboxes
44
- * `error-message` | The error message element
45
+ * Part name | Description
46
+ * ---------------------|----------------
47
+ * `label` | The slotted label element wrapper
48
+ * `group-field` | The checkbox elements wrapper
49
+ * `helper-text` | The slotted helper text element wrapper
50
+ * `error-message` | The slotted error message element wrapper
51
+ * `required-indicator` | The `required` state indicator element
45
52
  *
46
53
  * The following state attributes are available for styling:
47
54
  *
48
- * Attribute | Description | Part name
49
- * -----------|-------------|------------
50
- * `disabled` | Set when the checkbox group and its children are disabled. | :host
51
- * `focused` | Set when the checkbox group contains focus | :host
52
- * `has-label` | Set when the element has a label | :host
53
- * `has-value` | Set when the element has a value | :host
54
- * `has-helper` | Set when the element has helper text or slot | :host
55
- * `has-error-message` | Set when the element has an error message, regardless if the field is valid or not | :host
56
- * `required` | Set when the element is required | :host
57
- * `invalid` | Set when the element is invalid | :host
55
+ * Attribute | Description | Part name
56
+ * --------------------|-------------------------------------------|------------
57
+ * `disabled` | Set when the element is disabled | :host
58
+ * `invalid` | Set when the element is invalid | :host
59
+ * `focused` | Set when the element is focused | :host
60
+ * `has-label` | Set when the element has a label | :host
61
+ * `has-value` | Set when the element has a value | :host
62
+ * `has-helper` | Set when the element has helper text | :host
63
+ * `has-error-message` | Set when the element has an error message | :host
58
64
  *
59
65
  * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
60
66
  *
61
67
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
62
68
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
63
69
  */
64
- declare class CheckboxGroupElement extends ThemableMixin(DirMixin(HTMLElement)) {
70
+ declare class CheckboxGroup extends FieldMixin(FocusMixin(DisabledMixin(DirMixin(ThemableMixin(HTMLElement))))) {
65
71
  /**
66
- * The current disabled state of the checkbox group. True if group and all internal checkboxes are disabled.
67
- */
68
- disabled: boolean | null | undefined;
69
-
70
- /**
71
- * String used for the label element.
72
- */
73
- label: string | null | undefined;
74
-
75
- /**
76
- * Value of the checkbox group.
72
+ * The value of the checkbox group.
77
73
  * Note: toggling the checkboxes modifies the value by creating new
78
74
  * array each time, to override Polymer dirty-checking for arrays.
79
75
  * You can still use Polymer array mutation methods to update the value.
80
76
  */
81
77
  value: string[];
82
78
 
83
- /**
84
- * Error to show when the input value is invalid.
85
- * @attr {string} error-message
86
- */
87
- errorMessage: string | null | undefined;
88
-
89
- /**
90
- * String used for the helper text.
91
- * @attr {string} helper-text
92
- */
93
- helperText: string | null;
94
-
95
- /**
96
- * Specifies that the user must fill in a value.
97
- */
98
- required: boolean | null | undefined;
99
-
100
- /**
101
- * This property is set to true when the control value is invalid.
102
- */
103
- invalid: boolean;
104
-
105
- /**
106
- * Returns true if `value` is valid.
107
- *
108
- * @returns True if the value is valid.
109
- */
110
- validate(): boolean;
111
-
112
- _addCheckboxToValue(value: string): void;
113
-
114
- _removeCheckboxFromValue(value: string): void;
115
-
116
- _changeSelectedCheckbox(checkbox: CheckboxElement | null): void;
117
-
118
- _containsFocus(): boolean;
119
-
120
- _setFocused(focused: boolean): void;
121
-
122
79
  addEventListener<K extends keyof CheckboxGroupEventMap>(
123
80
  type: K,
124
- listener: (this: CheckboxGroupElement, ev: CheckboxGroupEventMap[K]) => void,
81
+ listener: (this: CheckboxGroup, ev: CheckboxGroupEventMap[K]) => void,
125
82
  options?: boolean | AddEventListenerOptions
126
83
  ): void;
127
84
 
128
85
  removeEventListener<K extends keyof CheckboxGroupEventMap>(
129
86
  type: K,
130
- listener: (this: CheckboxGroupElement, ev: CheckboxGroupEventMap[K]) => void,
87
+ listener: (this: CheckboxGroup, ev: CheckboxGroupEventMap[K]) => void,
131
88
  options?: boolean | EventListenerOptions
132
89
  ): void;
133
90
  }
134
91
 
135
92
  declare global {
136
93
  interface HTMLElementTagNameMap {
137
- 'vaadin-checkbox-group': CheckboxGroupElement;
94
+ 'vaadin-checkbox-group': CheckboxGroup;
138
95
  }
139
96
  }
140
97
 
141
- export { CheckboxGroupElement };
98
+ export { CheckboxGroup };
@@ -5,12 +5,15 @@
5
5
  */
6
6
  import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
7
7
  import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
8
+ import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
9
+ import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js';
10
+ import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
11
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
8
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
- import { DirMixin } from '@vaadin/vaadin-element-mixin/vaadin-dir-mixin.js';
10
- import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js';
13
+ import { Checkbox } from '@vaadin/checkbox/src/vaadin-checkbox.js';
11
14
 
12
15
  /**
13
- * `<vaadin-checkbox-group>` is a Polymer element for grouping vaadin-checkboxes.
16
+ * `<vaadin-checkbox-group>` is a web component that allows the user to choose several items from a group of binary choices.
14
17
  *
15
18
  * ```html
16
19
  * <vaadin-checkbox-group label="Preferred language of contact:">
@@ -24,24 +27,25 @@ import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js';
24
27
  *
25
28
  * The following shadow DOM parts are available for styling:
26
29
  *
27
- * Part name | Description
28
- * ----------------|----------------
29
- * `label` | The label element
30
- * `group-field` | The element that wraps checkboxes
31
- * `error-message` | The error message element
30
+ * Part name | Description
31
+ * ---------------------|----------------
32
+ * `label` | The slotted label element wrapper
33
+ * `group-field` | The checkbox elements wrapper
34
+ * `helper-text` | The slotted helper text element wrapper
35
+ * `error-message` | The slotted error message element wrapper
36
+ * `required-indicator` | The `required` state indicator element
32
37
  *
33
38
  * The following state attributes are available for styling:
34
39
  *
35
- * Attribute | Description | Part name
36
- * -----------|-------------|------------
37
- * `disabled` | Set when the checkbox group and its children are disabled. | :host
38
- * `focused` | Set when the checkbox group contains focus | :host
39
- * `has-label` | Set when the element has a label | :host
40
- * `has-value` | Set when the element has a value | :host
41
- * `has-helper` | Set when the element has helper text or slot | :host
42
- * `has-error-message` | Set when the element has an error message, regardless if the field is valid or not | :host
43
- * `required` | Set when the element is required | :host
44
- * `invalid` | Set when the element is invalid | :host
40
+ * Attribute | Description | Part name
41
+ * --------------------|-------------------------------------------|------------
42
+ * `disabled` | Set when the element is disabled | :host
43
+ * `invalid` | Set when the element is invalid | :host
44
+ * `focused` | Set when the element is focused | :host
45
+ * `has-label` | Set when the element has a label | :host
46
+ * `has-value` | Set when the element has a value | :host
47
+ * `has-helper` | Set when the element has helper text | :host
48
+ * `has-error-message` | Set when the element has an error message | :host
45
49
  *
46
50
  * See [Styling Components](https://vaadin.com/docs/latest/ds/customization/styling-components) documentation.
47
51
  *
@@ -51,9 +55,15 @@ import { CheckboxElement } from '@vaadin/checkbox/src/vaadin-checkbox.js';
51
55
  * @extends HTMLElement
52
56
  * @mixes ThemableMixin
53
57
  * @mixes DirMixin
54
- * @element vaadin-checkbox-group
58
+ * @mixes DisabledMixin
59
+ * @mixes FocusMixin
60
+ * @mixes FieldMixin
55
61
  */
56
- class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) {
62
+ class CheckboxGroup extends FieldMixin(FocusMixin(DisabledMixin(DirMixin(ThemableMixin(PolymerElement))))) {
63
+ static get is() {
64
+ return 'vaadin-checkbox-group';
65
+ }
66
+
57
67
  static get template() {
58
68
  return html`
59
69
  <style>
@@ -76,62 +86,36 @@ class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) {
76
86
  flex-direction: column;
77
87
  }
78
88
 
79
- [part='label']:empty {
89
+ :host(:not([has-label])) [part='label'] {
80
90
  display: none;
81
91
  }
82
92
  </style>
83
93
 
84
94
  <div class="vaadin-group-field-container">
85
- <label part="label">[[label]]</label>
95
+ <div part="label">
96
+ <slot name="label"></slot>
97
+ <span part="required-indicator" aria-hidden="true"></span>
98
+ </div>
86
99
 
87
100
  <div part="group-field">
88
- <slot id="slot"></slot>
101
+ <slot></slot>
89
102
  </div>
90
103
 
91
- <div
92
- part="helper-text"
93
- aria-live="assertive"
94
- aria-hidden$="[[_getHelperTextAriaHidden(helperText, _hasSlottedHelper)]]"
95
- >
96
- <slot name="helper">[[helperText]]</slot>
104
+ <div part="helper-text">
105
+ <slot name="helper"></slot>
97
106
  </div>
98
107
 
99
- <div
100
- part="error-message"
101
- aria-live="assertive"
102
- aria-hidden$="[[_getErrorMessageAriaHidden(invalid, errorMessage)]]"
103
- >[[errorMessage]]</div
104
- >
108
+ <div part="error-message">
109
+ <slot name="error-message"></slot>
110
+ </div>
105
111
  </div>
106
112
  `;
107
113
  }
108
114
 
109
- static get is() {
110
- return 'vaadin-checkbox-group';
111
- }
112
-
113
115
  static get properties() {
114
116
  return {
115
117
  /**
116
- * The current disabled state of the checkbox group. True if group and all internal checkboxes are disabled.
117
- */
118
- disabled: {
119
- type: Boolean,
120
- reflectToAttribute: true,
121
- observer: '_disabledChanged'
122
- },
123
-
124
- /**
125
- * String used for the label element.
126
- */
127
- label: {
128
- type: String,
129
- value: '',
130
- observer: '_labelChanged'
131
- },
132
-
133
- /**
134
- * Value of the checkbox group.
118
+ * The value of the checkbox group.
135
119
  * Note: toggling the checkboxes modifies the value by creating new
136
120
  * array each time, to override Polymer dirty-checking for arrays.
137
121
  * You can still use Polymer array mutation methods to update the value.
@@ -141,269 +125,232 @@ class CheckboxGroupElement extends ThemableMixin(DirMixin(PolymerElement)) {
141
125
  type: Array,
142
126
  value: () => [],
143
127
  notify: true
144
- },
145
-
146
- /**
147
- * Error to show when the input value is invalid.
148
- * @attr {string} error-message
149
- */
150
- errorMessage: {
151
- type: String,
152
- value: '',
153
- observer: '_errorMessageChanged'
154
- },
155
-
156
- /**
157
- * String used for the helper text.
158
- * @attr {string} helper-text
159
- * @type {string | null}
160
- */
161
- helperText: {
162
- type: String,
163
- value: '',
164
- observer: '_helperTextChanged'
165
- },
166
-
167
- /**
168
- * Specifies that the user must fill in a value.
169
- */
170
- required: {
171
- type: Boolean,
172
- reflectToAttribute: true
173
- },
174
-
175
- /**
176
- * This property is set to true when the control value is invalid.
177
- * @type {boolean}
178
- */
179
- invalid: {
180
- type: Boolean,
181
- reflectToAttribute: true,
182
- notify: true,
183
- value: false
184
- },
185
-
186
- /** @private */
187
- _hasSlottedHelper: Boolean
128
+ }
188
129
  };
189
130
  }
190
131
 
191
132
  static get observers() {
192
- return ['_updateValue(value, value.splices)'];
133
+ return ['__valueChanged(value, value.splices)'];
193
134
  }
194
135
 
195
- ready() {
196
- super.ready();
136
+ constructor() {
137
+ super();
197
138
 
198
- this.addEventListener('focusin', () => this._setFocused(this._containsFocus()));
199
-
200
- this.addEventListener('focusout', (e) => {
201
- // validate when stepping out of the checkbox group
202
- if (
203
- !this._checkboxes.some(
204
- (checkbox) => e.relatedTarget === checkbox || checkbox.shadowRoot.contains(e.relatedTarget)
205
- )
206
- ) {
207
- this.validate();
208
- this._setFocused(false);
209
- }
210
- });
139
+ this.__registerCheckbox = this.__registerCheckbox.bind(this);
140
+ this.__unregisterCheckbox = this.__unregisterCheckbox.bind(this);
141
+ this.__onCheckboxCheckedChanged = this.__onCheckboxCheckedChanged.bind(this);
142
+ }
211
143
 
212
- const checkedChangedListener = (e) => {
213
- this._changeSelectedCheckbox(e.target);
214
- };
144
+ ready() {
145
+ super.ready();
215
146
 
216
- this._observer = new FlattenedNodesObserver(this, (info) => {
217
- const addedCheckboxes = this._filterCheckboxes(info.addedNodes);
147
+ this.ariaTarget = this;
218
148
 
219
- addedCheckboxes.forEach((checkbox) => {
220
- checkbox.addEventListener('checked-changed', checkedChangedListener);
221
- if (this.disabled) {
222
- checkbox.disabled = true;
223
- }
224
- if (checkbox.checked) {
225
- this._addCheckboxToValue(checkbox.value);
226
- } else if (this.value.indexOf(checkbox.value) > -1) {
227
- checkbox.checked = true;
228
- }
229
- });
149
+ // See https://github.com/vaadin/vaadin-web-components/issues/94
150
+ this.setAttribute('role', 'group');
230
151
 
231
- this._filterCheckboxes(info.removedNodes).forEach((checkbox) => {
232
- checkbox.removeEventListener('checked-changed', checkedChangedListener);
233
- if (checkbox.checked) {
234
- this._removeCheckboxFromValue(checkbox.value);
235
- }
236
- });
152
+ this._observer = new FlattenedNodesObserver(this, ({ addedNodes, removedNodes }) => {
153
+ const addedCheckboxes = this.__filterCheckboxes(addedNodes);
154
+ const removedCheckboxes = this.__filterCheckboxes(removedNodes);
237
155
 
238
- this._setOrToggleHasHelperAttribute();
156
+ addedCheckboxes.forEach(this.__registerCheckbox);
157
+ removedCheckboxes.forEach(this.__unregisterCheckbox);
239
158
 
240
- const hasValue = (checkbox) => {
241
- const { value } = checkbox;
242
- return checkbox.hasAttribute('value') || (value && value !== 'on');
243
- };
244
- if (!addedCheckboxes.every(hasValue)) {
245
- console.warn('Please add value attribute to all checkboxes in checkbox group');
246
- }
159
+ this.__warnOfCheckboxesWithoutValue(addedCheckboxes);
247
160
  });
248
161
  }
249
162
 
250
163
  /**
251
- * Returns true if `value` is valid.
164
+ * Override method inherited from `ValidateMixin`
165
+ * to validate the value array.
252
166
  *
253
- * @return {boolean} True if the value is valid.
167
+ * @override
168
+ * @return {boolean}
254
169
  */
255
- validate() {
256
- this.invalid = this.required && this.value.length === 0;
257
- return !this.invalid;
170
+ checkValidity() {
171
+ return !this.required || this.value.length > 0;
258
172
  }
259
173
 
260
- /** @private */
261
- get _checkboxes() {
262
- return this._filterCheckboxes(this.querySelectorAll('*'));
174
+ /**
175
+ * @param {!Array<!Node>} nodes
176
+ * @return {!Array<!Checkbox>}
177
+ * @private
178
+ */
179
+ __filterCheckboxes(nodes) {
180
+ return nodes.filter((child) => child instanceof Checkbox);
263
181
  }
264
182
 
265
- /** @private */
266
- _filterCheckboxes(nodes) {
267
- return Array.from(nodes).filter((child) => child instanceof CheckboxElement);
183
+ /**
184
+ * A collection of the checkboxes.
185
+ *
186
+ * @return {!Array<!Checkbox>}
187
+ * @private
188
+ */
189
+ get __checkboxes() {
190
+ return this.__filterCheckboxes([...this.children]);
268
191
  }
269
192
 
270
- /** @private */
271
- _disabledChanged(disabled) {
272
- this.setAttribute('aria-disabled', disabled);
193
+ /**
194
+ * @param {!Array<!Checkbox>} checkboxes
195
+ * @private
196
+ */
197
+ __warnOfCheckboxesWithoutValue(checkboxes) {
198
+ const hasCheckboxesWithoutValue = checkboxes.some((checkbox) => {
199
+ const { value } = checkbox;
200
+
201
+ return !checkbox.hasAttribute('value') && (!value || value === 'on');
202
+ });
273
203
 
274
- this._checkboxes.forEach((checkbox) => (checkbox.disabled = disabled));
204
+ if (hasCheckboxesWithoutValue) {
205
+ console.warn('Please provide the value attribute to all the checkboxes inside the checkbox group.');
206
+ }
275
207
  }
276
208
 
277
209
  /**
278
- * @param {string} value
279
- * @protected
210
+ * Registers the checkbox after adding it to the group.
211
+ *
212
+ * @param {!Checkbox} checkbox
213
+ * @private
280
214
  */
281
- _addCheckboxToValue(value) {
282
- if (this.value.indexOf(value) === -1) {
283
- this.value = this.value.concat(value);
215
+ __registerCheckbox(checkbox) {
216
+ checkbox.addEventListener('checked-changed', this.__onCheckboxCheckedChanged);
217
+
218
+ if (this.disabled) {
219
+ checkbox.disabled = true;
220
+ }
221
+
222
+ if (checkbox.checked) {
223
+ this.__addCheckboxToValue(checkbox.value);
224
+ } else if (this.value.includes(checkbox.value)) {
225
+ checkbox.checked = true;
284
226
  }
285
227
  }
286
228
 
287
229
  /**
288
- * @param {string} value
289
- * @protected
230
+ * Unregisters the checkbox before removing it from the group.
231
+ *
232
+ * @param {!Checkbox} checkbox
233
+ * @private
290
234
  */
291
- _removeCheckboxFromValue(value) {
292
- this.value = this.value.filter((v) => v !== value);
235
+ __unregisterCheckbox(checkbox) {
236
+ checkbox.removeEventListener('checked-changed', this.__onCheckboxCheckedChanged);
237
+
238
+ if (checkbox.checked) {
239
+ this.__removeCheckboxFromValue(checkbox.value);
240
+ }
293
241
  }
294
242
 
295
243
  /**
296
- * @param {CheckboxElement} checkbox
244
+ * Override method inherited from `DisabledMixin`
245
+ * to propagate the `disabled` property to the checkboxes.
246
+ *
247
+ * @param {boolean} newValue
248
+ * @param {boolean} oldValue
249
+ * @override
297
250
  * @protected
298
251
  */
299
- _changeSelectedCheckbox(checkbox) {
300
- if (this._updatingValue) {
252
+ _disabledChanged(newValue, oldValue) {
253
+ super._disabledChanged(newValue, oldValue);
254
+
255
+ // Prevent updating the `disabled` property for the checkboxes at initialization.
256
+ // Otherwise, the checkboxes may end up enabled regardless the `disabled` attribute
257
+ // intentionally added by the user on some of them.
258
+ if (!newValue && oldValue === undefined) {
301
259
  return;
302
260
  }
303
261
 
304
- if (checkbox.checked) {
305
- this._addCheckboxToValue(checkbox.value);
306
- } else {
307
- this._removeCheckboxFromValue(checkbox.value);
262
+ if (oldValue !== newValue) {
263
+ this.__checkboxes.forEach((checkbox) => {
264
+ checkbox.disabled = newValue;
265
+ });
308
266
  }
309
267
  }
310
268
 
311
- /** @private */
312
- _updateValue(value) {
313
- // setting initial value to empty array, skip validation
314
- if (value.length === 0 && this._oldValue === undefined) {
315
- return;
316
- }
317
-
318
- if (value.length) {
319
- this.setAttribute('has-value', '');
320
- } else {
321
- this.removeAttribute('has-value');
269
+ /**
270
+ * @param {string} value
271
+ * @private
272
+ */
273
+ __addCheckboxToValue(value) {
274
+ if (!this.value.includes(value)) {
275
+ this.value = [...this.value, value];
322
276
  }
323
-
324
- this._oldValue = value;
325
- // set a flag to avoid updating loop
326
- this._updatingValue = true;
327
- // reflect the value array to checkboxes
328
- this._checkboxes.forEach((checkbox) => {
329
- checkbox.checked = value.indexOf(checkbox.value) > -1;
330
- });
331
- this._updatingValue = false;
332
-
333
- this.validate();
334
277
  }
335
278
 
336
- /** @private */
337
- _labelChanged(label) {
338
- this._setOrToggleAttribute('has-label', !!label);
279
+ /**
280
+ * @param {string} value
281
+ * @private
282
+ */
283
+ __removeCheckboxFromValue(value) {
284
+ if (this.value.includes(value)) {
285
+ this.value = this.value.filter((v) => v !== value);
286
+ }
339
287
  }
340
288
 
341
- /** @private */
342
- _errorMessageChanged(errorMessage) {
343
- this._setOrToggleAttribute('has-error-message', !!errorMessage);
344
- }
289
+ /**
290
+ * @param {!CustomEvent} event
291
+ * @private
292
+ */
293
+ __onCheckboxCheckedChanged(event) {
294
+ const checkbox = event.target;
345
295
 
346
- /** @private */
347
- _helperTextChanged(helperText) {
348
- this._setOrToggleAttribute('has-helper', !!helperText);
296
+ if (checkbox.checked) {
297
+ this.__addCheckboxToValue(checkbox.value);
298
+ } else {
299
+ this.__removeCheckboxFromValue(checkbox.value);
300
+ }
349
301
  }
350
302
 
351
- /** @private */
352
- _setOrToggleAttribute(name, value) {
353
- if (!name) {
303
+ /**
304
+ * @param {string | null | undefined} value
305
+ * @private
306
+ */
307
+ __valueChanged(value) {
308
+ // setting initial value to empty array, skip validation
309
+ if (value.length === 0 && this.__oldValue === undefined) {
354
310
  return;
355
311
  }
356
312
 
357
- if (value) {
358
- this.setAttribute(name, typeof value === 'boolean' ? '' : value);
359
- } else {
360
- this.removeAttribute(name);
361
- }
362
- }
313
+ this.__oldValue = value;
314
+
315
+ this.toggleAttribute('has-value', value.length > 0);
316
+
317
+ this.__checkboxes.forEach((checkbox) => {
318
+ checkbox.checked = value.includes(checkbox.value);
319
+ });
363
320
 
364
- /** @private */
365
- _getErrorMessageAriaHidden(invalid, errorMessage) {
366
- return (!errorMessage || !invalid).toString();
321
+ this.validate();
367
322
  }
368
323
 
369
324
  /**
325
+ * Override method inherited from `FocusMixin`
326
+ * to prevent removing the `focused` attribute
327
+ * when focus moves between checkboxes inside the group.
328
+ *
329
+ * @param {!FocusEvent} event
370
330
  * @return {boolean}
371
331
  * @protected
372
332
  */
373
- _containsFocus() {
374
- const activeElement = this.getRootNode().activeElement;
375
- return this.contains(activeElement);
333
+ _shouldRemoveFocus(event) {
334
+ return !this.contains(event.relatedTarget);
376
335
  }
377
336
 
378
337
  /**
338
+ * Override method inherited from `FocusMixin`
339
+ * to run validation when the group loses focus.
340
+ *
379
341
  * @param {boolean} focused
342
+ * @override
380
343
  * @protected
381
344
  */
382
345
  _setFocused(focused) {
383
- if (focused) {
384
- this.setAttribute('focused', '');
385
- } else {
386
- this.removeAttribute('focused');
387
- }
388
- }
389
-
390
- /** @private */
391
- _setOrToggleHasHelperAttribute() {
392
- const slottedNodes = this.shadowRoot.querySelector(`[name="helper"]`).assignedNodes();
393
- // Only has slotted helper if not a text node
394
- // Text nodes are added by the helperText prop and not the helper slot
395
- // The filter is added due to shady DOM triggering this slotchange event on helperText prop change
396
- this._hasSlottedHelper = slottedNodes.filter((node) => node.nodeType !== 3).length > 0;
346
+ super._setFocused(focused);
397
347
 
398
- this._setOrToggleAttribute('has-helper', this._hasSlottedHelper ? 'slotted' : !!this.helperText);
399
- }
400
-
401
- /** @private */
402
- _getHelperTextAriaHidden(helperText, hasSlottedHelper) {
403
- return (!(helperText || hasSlottedHelper)).toString();
348
+ if (!focused) {
349
+ this.validate();
350
+ }
404
351
  }
405
352
  }
406
353
 
407
- customElements.define(CheckboxGroupElement.is, CheckboxGroupElement);
354
+ customElements.define(CheckboxGroup.is, CheckboxGroup);
408
355
 
409
- export { CheckboxGroupElement };
356
+ export { CheckboxGroup };
@@ -1,5 +1,10 @@
1
1
  import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js';
2
2
  import '@vaadin/vaadin-lumo-styles/color.js';
3
+ import '@vaadin/vaadin-lumo-styles/sizing.js';
4
+ import '@vaadin/vaadin-lumo-styles/spacing.js';
5
+ import '@vaadin/vaadin-lumo-styles/style.js';
6
+ import '@vaadin/vaadin-lumo-styles/typography.js';
7
+ import { helper } from '@vaadin/vaadin-lumo-styles/mixins/helper.js';
3
8
  import { requiredField } from '@vaadin/vaadin-lumo-styles/mixins/required-field.js';
4
9
 
5
10
  const checkboxGroup = css`
@@ -14,7 +19,8 @@ const checkboxGroup = css`
14
19
  }
15
20
 
16
21
  :host::before {
17
- height: var(--lumo-size-m);
22
+ /* Effective height of vaadin-checkbox */
23
+ height: var(--lumo-size-s);
18
24
  box-sizing: border-box;
19
25
  display: inline-flex;
20
26
  align-items: center;
@@ -25,10 +31,6 @@ const checkboxGroup = css`
25
31
  flex-direction: column;
26
32
  }
27
33
 
28
- [part='label'] {
29
- padding-bottom: 0.7em;
30
- }
31
-
32
34
  :host([disabled]) [part='label'] {
33
35
  color: var(--lumo-disabled-text-color);
34
36
  -webkit-text-fill-color: var(--lumo-disabled-text-color);
@@ -39,55 +41,10 @@ const checkboxGroup = css`
39
41
  }
40
42
 
41
43
  :host(:hover:not([disabled]):not([focused])) [part='label'],
42
- :host(:hover:not([disabled]):not([focused])) [part='helper-text'],
43
- :host(:hover:not([disabled]):not([focused])) [part='helper-text'] ::slotted(*) {
44
+ :host(:hover:not([disabled]):not([focused])) [part='helper-text'] {
44
45
  color: var(--lumo-body-text-color);
45
46
  }
46
47
 
47
- :host([has-helper]) [part='helper-text']::before {
48
- content: '';
49
- display: block;
50
- height: 0.4em;
51
- }
52
-
53
- [part='helper-text'],
54
- [part='helper-text'] ::slotted(*) {
55
- display: block;
56
- color: var(--lumo-secondary-text-color);
57
- font-size: var(--lumo-font-size-xs);
58
- line-height: var(--lumo-line-height-xs);
59
- margin-left: calc(var(--lumo-border-radius-m) / 4);
60
- transition: color 0.2s;
61
- }
62
-
63
- /* helper-text position */
64
- :host([has-helper][theme~='helper-above-field']) [part='helper-text']::before {
65
- display: none;
66
- }
67
-
68
- :host([has-helper][theme~='helper-above-field']) [part='helper-text']::after {
69
- content: '';
70
- display: block;
71
- height: 0.4em;
72
- }
73
-
74
- :host([has-helper][theme~='helper-above-field']) [part='label'] {
75
- order: 0;
76
- padding-bottom: 0.4em;
77
- }
78
-
79
- :host([has-helper][theme~='helper-above-field']) [part='helper-text'] {
80
- order: 1;
81
- }
82
-
83
- :host([has-helper][theme~='helper-above-field']) [part='group-field'] {
84
- order: 2;
85
- }
86
-
87
- :host([has-helper][theme~='helper-above-field']) [part='error-message'] {
88
- order: 3;
89
- }
90
-
91
48
  /* Touch device adjustment */
92
49
  @media (pointer: coarse) {
93
50
  :host(:hover:not([disabled]):not([focused])) [part='label'] {
@@ -96,6 +53,6 @@ const checkboxGroup = css`
96
53
  }
97
54
  `;
98
55
 
99
- registerStyles('vaadin-checkbox-group', [requiredField, checkboxGroup], {
56
+ registerStyles('vaadin-checkbox-group', [requiredField, helper, checkboxGroup], {
100
57
  moduleId: 'lumo-checkbox-group'
101
58
  });
@@ -1,5 +1,6 @@
1
1
  import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js';
2
2
  import '@vaadin/vaadin-material-styles/color.js';
3
+ import { helper } from '@vaadin/vaadin-material-styles/mixins/helper.js';
3
4
  import { requiredField } from '@vaadin/vaadin-material-styles/mixins/required-field.js';
4
5
 
5
6
  const checkboxGroup = css`
@@ -25,15 +26,6 @@ const checkboxGroup = css`
25
26
  padding-top: 24px;
26
27
  }
27
28
 
28
- [part='label']:empty {
29
- display: none;
30
- }
31
-
32
- [part='label']:empty::before {
33
- content: '\\00a0';
34
- position: absolute;
35
- }
36
-
37
29
  :host([theme~='vertical']) [part='group-field'] {
38
30
  display: flex;
39
31
  flex-direction: column;
@@ -47,26 +39,8 @@ const checkboxGroup = css`
47
39
  :host([focused]:not([invalid])) [part='label'] {
48
40
  color: var(--material-primary-text-color);
49
41
  }
50
-
51
- /* According to material theme guidelines, helper text should be hidden when error message is set and input is invalid */
52
- :host([has-helper][invalid][has-error-message]) [part='helper-text'] {
53
- display: none;
54
- }
55
-
56
- :host([has-helper]) [part='helper-text']::before {
57
- content: '';
58
- display: block;
59
- height: 6px;
60
- }
61
-
62
- [part='helper-text'],
63
- [part='helper-text'] ::slotted(*) {
64
- font-size: 0.75rem;
65
- line-height: 1;
66
- color: var(--material-secondary-text-color);
67
- }
68
42
  `;
69
43
 
70
- registerStyles('vaadin-checkbox-group', [requiredField, checkboxGroup], {
44
+ registerStyles('vaadin-checkbox-group', [requiredField, helper, checkboxGroup], {
71
45
  moduleId: 'material-checkbox-group'
72
46
  });