UNPKG

7.08 kBJavaScriptView Raw
1/**
2 * Copyright IBM Corp. 2016, 2018
3 *
4 * This source code is licensed under the Apache-2.0 license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import settings from '../../globals/js/settings';
9import mixin from '../../globals/js/misc/mixin';
10import createComponent from '../../globals/js/mixins/create-component';
11import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
12import handles from '../../globals/js/mixins/handles';
13import on from '../../globals/js/misc/on';
14
15const stateChangeTypes = {
16 true: 'true',
17 false: 'false',
18 mixed: 'mixed',
19};
20
21class Checkbox extends mixin(createComponent, initComponentBySearch, handles) {
22 /**
23 * Checkbox UI.
24 * @extends CreateComponent
25 * @extends InitComponentBySearch
26 * @extends Handles
27 * @param {HTMLElement} element The element working as a checkbox UI.
28 */
29
30 constructor(element, options) {
31 super(element, options);
32 this.manage(
33 on(this.element, 'click', event => {
34 this._handleClick(event);
35 })
36 );
37 this.manage(
38 on(this.element, 'focus', event => {
39 this._handleFocus(event);
40 })
41 );
42 this.manage(
43 on(this.element, 'blur', event => {
44 this._handleBlur(event);
45 })
46 );
47
48 this._indeterminateCheckbox();
49 this._initCheckbox();
50 }
51
52 _handleClick() {
53 if (this.element.checked === true) {
54 this.element.setAttribute('checked', '');
55 this.element.setAttribute('aria-checked', 'true');
56 this.element.checked = true;
57
58 // nested checkboxes inside labels
59 if (this.element.parentElement.classList.contains(this.options.classLabel)) {
60 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxState, 'true');
61 }
62 } else if (this.element.checked === false) {
63 this.element.removeAttribute('checked');
64 this.element.setAttribute('aria-checked', 'false');
65 this.element.checked = false;
66
67 // nested checkboxes inside labels
68 if (this.element.parentElement.classList.contains(this.options.classLabel)) {
69 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxState, 'false');
70 }
71 }
72 }
73
74 _handleFocus() {
75 if (this.element.parentElement.classList.contains(this.options.classLabel)) {
76 this.element.parentElement.classList.add(this.options.classLabelFocused);
77 }
78 }
79
80 _handleBlur() {
81 if (this.element.parentElement.classList.contains(this.options.classLabel)) {
82 this.element.parentElement.classList.remove(this.options.classLabelFocused);
83 }
84 }
85
86 /**
87 * Sets the new checkbox state.
88 * @param {boolean|string} [state]
89 * The new checkbox state to set. `mixed` to put checkbox in indeterminate state.
90 * If omitted, this method simply makes the style reflect `aria-checked` attribute.
91 */
92 setState(state) {
93 if (state === undefined || stateChangeTypes[state] === undefined) {
94 throw new TypeError('setState expects a value of true, false or mixed.');
95 }
96
97 this.element.setAttribute('aria-checked', state);
98 this.element.indeterminate = state === stateChangeTypes.mixed;
99 this.element.checked = state === stateChangeTypes.true;
100
101 const container = this.element.closest(this.options.selectorContainedCheckboxState);
102 if (container) {
103 container.setAttribute(this.options.attribContainedCheckboxState, state);
104 }
105 }
106
107 setDisabled(value) {
108 if (value === undefined) {
109 throw new TypeError('setDisabled expects a boolean value of true or false');
110 }
111 if (value === true) {
112 this.element.setAttribute('disabled', true);
113 } else if (value === false) {
114 this.element.removeAttribute('disabled');
115 }
116 const container = this.element.closest(this.options.selectorContainedCheckboxDisabled);
117 if (container) {
118 container.setAttribute(this.options.attribContainedCheckboxDisabled, value);
119 }
120 }
121
122 _indeterminateCheckbox() {
123 if (this.element.getAttribute('aria-checked') === 'mixed') {
124 this.element.indeterminate = true;
125 }
126 if (this.element.indeterminate === true) {
127 this.element.setAttribute('aria-checked', 'mixed');
128 }
129 if (this.element.parentElement.classList.contains(this.options.classLabel) && this.element.indeterminate === true) {
130 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxState, 'mixed');
131 }
132 }
133
134 _initCheckbox() {
135 if (this.element.checked === true) {
136 this.element.setAttribute('aria-checked', 'true');
137 }
138 if (this.element.parentElement.classList.contains(this.options.classLabel) && this.element.checked) {
139 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxState, 'true');
140 }
141 if (this.element.parentElement.classList.contains(this.options.classLabel)) {
142 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxDisabled, 'false');
143 }
144 if (this.element.parentElement.classList.contains(this.options.classLabel) && this.element.disabled) {
145 this.element.parentElement.setAttribute(this.options.attribContainedCheckboxDisabled, 'true');
146 }
147 }
148
149 /**
150 * The map associating DOM element and copy button UI instance.
151 * @member Checkbox.components
152 * @type {WeakMap}
153 */
154 static components /* #__PURE_CLASS_PROPERTY__ */ = new WeakMap();
155
156 /**
157 * The component options.
158 * If `options` is specified in the constructor, {@linkcode Checkbox.create .create()}, or {@linkcode Checkbox.init .init()},
159 * properties in this object are overriden for the instance being create and how {@linkcode Checkbox.init .init()} works.
160 * @member Checkbox.options
161 * @type {Object}
162 * @property {string} selectorInit The data attribute to find copy button UIs.
163 * @property {string} selectorContainedCheckboxState The CSS selector to find a container of checkbox preserving checked state.
164 * @property {string} selectorContainedCheckboxDisabled
165 * The CSS selector to find a container of checkbox preserving disabled state.
166 * @property {string} classLabel The CSS class for the label.
167 * @property {string} classLabelFocused The CSS class for the focused label.
168 * @property {string} attribContainedCheckboxState The attribute name for the checked state of contained checkbox.
169 * @property {string} attribContainedCheckboxDisabled The attribute name for the disabled state of contained checkbox.
170 */
171 static get options() {
172 const { prefix } = settings;
173 return {
174 selectorInit: `.${prefix}--checkbox`,
175 selectorContainedCheckboxState: '[data-contained-checkbox-state]',
176 selectorContainedCheckboxDisabled: '[data-contained-checkbox-disabled]',
177 classLabel: `${prefix}--checkbox-label`,
178 classLabelFocused: `${prefix}--checkbox-label__focus`,
179 attribContainedCheckboxState: 'data-contained-checkbox-state',
180 attribContainedCheckboxDisabled: 'data-contained-checkbox-disabled',
181 };
182 }
183
184 static stateChangeTypes /* #__PURE_CLASS_PROPERTY__ */ = stateChangeTypes;
185}
186
187export default Checkbox;