@vaadin/checkbox-group 22.0.0-alpha5 → 22.0.0-alpha9

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-alpha5",
3
+ "version": "22.0.0-alpha9",
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-alpha5",
30
- "@vaadin/vaadin-element-mixin": "^22.0.0-alpha5",
31
- "@vaadin/vaadin-lumo-styles": "^22.0.0-alpha5",
32
- "@vaadin/vaadin-material-styles": "^22.0.0-alpha5",
33
- "@vaadin/vaadin-themable-mixin": "^22.0.0-alpha5"
36
+ "@vaadin/checkbox": "22.0.0-alpha9",
37
+ "@vaadin/component-base": "22.0.0-alpha9",
38
+ "@vaadin/vaadin-lumo-styles": "22.0.0-alpha9",
39
+ "@vaadin/vaadin-material-styles": "22.0.0-alpha9",
40
+ "@vaadin/vaadin-themable-mixin": "22.0.0-alpha9"
34
41
  },
35
42
  "devDependencies": {
36
43
  "@esm-bundle/chai": "^4.3.4",
37
- "@vaadin/testing-helpers": "^0.2.1",
44
+ "@vaadin/testing-helpers": "^0.3.0",
38
45
  "sinon": "^9.2.0"
39
46
  },
40
- "publishConfig": {
41
- "access": "public"
42
- },
43
- "gitHead": "012f658db6f81375be8889f63ee15e3f660fe9ec"
47
+ "gitHead": "6e8c899dc65918f97e3c0acb2076122c4b2ef274"
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,241 @@ 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.
252
- *
253
- * @return {boolean} True if the value is valid.
164
+ * @return {string}
165
+ * @override
166
+ * @protected
254
167
  */
255
- validate() {
256
- this.invalid = this.required && this.value.length === 0;
257
- return !this.invalid;
168
+ get _ariaAttr() {
169
+ return 'aria-labelledby';
258
170
  }
259
171
 
260
- /** @private */
261
- get _checkboxes() {
262
- return this._filterCheckboxes(this.querySelectorAll('*'));
172
+ /**
173
+ * Override method inherited from `ValidateMixin`
174
+ * to validate the value array.
175
+ *
176
+ * @override
177
+ * @return {boolean}
178
+ */
179
+ checkValidity() {
180
+ return !this.required || this.value.length > 0;
263
181
  }
264
182
 
265
- /** @private */
266
- _filterCheckboxes(nodes) {
267
- return Array.from(nodes).filter((child) => child instanceof CheckboxElement);
183
+ /**
184
+ * @param {!Array<!Node>} nodes
185
+ * @return {!Array<!Checkbox>}
186
+ * @private
187
+ */
188
+ __filterCheckboxes(nodes) {
189
+ return nodes.filter((child) => child instanceof Checkbox);
268
190
  }
269
191
 
270
- /** @private */
271
- _disabledChanged(disabled) {
272
- this.setAttribute('aria-disabled', disabled);
273
-
274
- this._checkboxes.forEach((checkbox) => (checkbox.disabled = disabled));
192
+ /**
193
+ * A collection of the checkboxes.
194
+ *
195
+ * @return {!Array<!Checkbox>}
196
+ * @private
197
+ */
198
+ get __checkboxes() {
199
+ return this.__filterCheckboxes([...this.children]);
275
200
  }
276
201
 
277
202
  /**
278
- * @param {string} value
279
- * @protected
203
+ * @param {!Array<!Checkbox>} checkboxes
204
+ * @private
280
205
  */
281
- _addCheckboxToValue(value) {
282
- if (this.value.indexOf(value) === -1) {
283
- this.value = this.value.concat(value);
206
+ __warnOfCheckboxesWithoutValue(checkboxes) {
207
+ const hasCheckboxesWithoutValue = checkboxes.some((checkbox) => {
208
+ const { value } = checkbox;
209
+
210
+ return !checkbox.hasAttribute('value') && (!value || value === 'on');
211
+ });
212
+
213
+ if (hasCheckboxesWithoutValue) {
214
+ console.warn('Please provide the value attribute to all the checkboxes inside the checkbox group.');
284
215
  }
285
216
  }
286
217
 
287
218
  /**
288
- * @param {string} value
289
- * @protected
219
+ * Registers the checkbox after adding it to the group.
220
+ *
221
+ * @param {!Checkbox} checkbox
222
+ * @private
290
223
  */
291
- _removeCheckboxFromValue(value) {
292
- this.value = this.value.filter((v) => v !== value);
224
+ __registerCheckbox(checkbox) {
225
+ checkbox.addEventListener('checked-changed', this.__onCheckboxCheckedChanged);
226
+
227
+ if (this.disabled) {
228
+ checkbox.disabled = true;
229
+ }
230
+
231
+ if (checkbox.checked) {
232
+ this.__addCheckboxToValue(checkbox.value);
233
+ } else if (this.value.includes(checkbox.value)) {
234
+ checkbox.checked = true;
235
+ }
293
236
  }
294
237
 
295
238
  /**
296
- * @param {CheckboxElement} checkbox
297
- * @protected
239
+ * Unregisters the checkbox before removing it from the group.
240
+ *
241
+ * @param {!Checkbox} checkbox
242
+ * @private
298
243
  */
299
- _changeSelectedCheckbox(checkbox) {
300
- if (this._updatingValue) {
301
- return;
302
- }
244
+ __unregisterCheckbox(checkbox) {
245
+ checkbox.removeEventListener('checked-changed', this.__onCheckboxCheckedChanged);
303
246
 
304
247
  if (checkbox.checked) {
305
- this._addCheckboxToValue(checkbox.value);
306
- } else {
307
- this._removeCheckboxFromValue(checkbox.value);
248
+ this.__removeCheckboxFromValue(checkbox.value);
308
249
  }
309
250
  }
310
251
 
311
- /** @private */
312
- _updateValue(value) {
313
- // setting initial value to empty array, skip validation
314
- if (value.length === 0 && this._oldValue === undefined) {
252
+ /**
253
+ * Override method inherited from `DisabledMixin`
254
+ * to propagate the `disabled` property to the checkboxes.
255
+ *
256
+ * @param {boolean} newValue
257
+ * @param {boolean} oldValue
258
+ * @override
259
+ * @protected
260
+ */
261
+ _disabledChanged(newValue, oldValue) {
262
+ super._disabledChanged(newValue, oldValue);
263
+
264
+ // Prevent updating the `disabled` property for the checkboxes at initialization.
265
+ // Otherwise, the checkboxes may end up enabled regardless the `disabled` attribute
266
+ // intentionally added by the user on some of them.
267
+ if (!newValue && oldValue === undefined) {
315
268
  return;
316
269
  }
317
270
 
318
- if (value.length) {
319
- this.setAttribute('has-value', '');
320
- } else {
321
- this.removeAttribute('has-value');
271
+ if (oldValue !== newValue) {
272
+ this.__checkboxes.forEach((checkbox) => {
273
+ checkbox.disabled = newValue;
274
+ });
322
275
  }
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
276
  }
335
277
 
336
- /** @private */
337
- _labelChanged(label) {
338
- this._setOrToggleAttribute('has-label', !!label);
278
+ /**
279
+ * @param {string} value
280
+ * @private
281
+ */
282
+ __addCheckboxToValue(value) {
283
+ if (!this.value.includes(value)) {
284
+ this.value = [...this.value, value];
285
+ }
339
286
  }
340
287
 
341
- /** @private */
342
- _errorMessageChanged(errorMessage) {
343
- this._setOrToggleAttribute('has-error-message', !!errorMessage);
288
+ /**
289
+ * @param {string} value
290
+ * @private
291
+ */
292
+ __removeCheckboxFromValue(value) {
293
+ if (this.value.includes(value)) {
294
+ this.value = this.value.filter((v) => v !== value);
295
+ }
344
296
  }
345
297
 
346
- /** @private */
347
- _helperTextChanged(helperText) {
348
- this._setOrToggleAttribute('has-helper', !!helperText);
298
+ /**
299
+ * @param {!CustomEvent} event
300
+ * @private
301
+ */
302
+ __onCheckboxCheckedChanged(event) {
303
+ const checkbox = event.target;
304
+
305
+ if (checkbox.checked) {
306
+ this.__addCheckboxToValue(checkbox.value);
307
+ } else {
308
+ this.__removeCheckboxFromValue(checkbox.value);
309
+ }
349
310
  }
350
311
 
351
- /** @private */
352
- _setOrToggleAttribute(name, value) {
353
- if (!name) {
312
+ /**
313
+ * @param {string | null | undefined} value
314
+ * @private
315
+ */
316
+ __valueChanged(value) {
317
+ // setting initial value to empty array, skip validation
318
+ if (value.length === 0 && this.__oldValue === undefined) {
354
319
  return;
355
320
  }
356
321
 
357
- if (value) {
358
- this.setAttribute(name, typeof value === 'boolean' ? '' : value);
359
- } else {
360
- this.removeAttribute(name);
361
- }
362
- }
322
+ this.__oldValue = value;
323
+
324
+ this.toggleAttribute('has-value', value.length > 0);
325
+
326
+ this.__checkboxes.forEach((checkbox) => {
327
+ checkbox.checked = value.includes(checkbox.value);
328
+ });
363
329
 
364
- /** @private */
365
- _getErrorMessageAriaHidden(invalid, errorMessage) {
366
- return (!errorMessage || !invalid).toString();
330
+ this.validate();
367
331
  }
368
332
 
369
333
  /**
334
+ * Override method inherited from `FocusMixin`
335
+ * to prevent removing the `focused` attribute
336
+ * when focus moves between checkboxes inside the group.
337
+ *
338
+ * @param {!FocusEvent} event
370
339
  * @return {boolean}
371
340
  * @protected
372
341
  */
373
- _containsFocus() {
374
- const activeElement = this.getRootNode().activeElement;
375
- return this.contains(activeElement);
342
+ _shouldRemoveFocus(event) {
343
+ return !this.contains(event.relatedTarget);
376
344
  }
377
345
 
378
346
  /**
347
+ * Override method inherited from `FocusMixin`
348
+ * to run validation when the group loses focus.
349
+ *
379
350
  * @param {boolean} focused
351
+ * @override
380
352
  * @protected
381
353
  */
382
354
  _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;
355
+ super._setFocused(focused);
397
356
 
398
- this._setOrToggleAttribute('has-helper', this._hasSlottedHelper ? 'slotted' : !!this.helperText);
399
- }
400
-
401
- /** @private */
402
- _getHelperTextAriaHidden(helperText, hasSlottedHelper) {
403
- return (!(helperText || hasSlottedHelper)).toString();
357
+ if (!focused) {
358
+ this.validate();
359
+ }
404
360
  }
405
361
  }
406
362
 
407
- customElements.define(CheckboxGroupElement.is, CheckboxGroupElement);
363
+ customElements.define(CheckboxGroup.is, CheckboxGroup);
408
364
 
409
- export { CheckboxGroupElement };
365
+ 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
  });