UNPKG

6.91 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2016 Google Inc.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 */
23
24import MDCFoundation from '@material/base/foundation';
25/* eslint-disable no-unused-vars */
26import {MDCIconToggleAdapter, IconToggleEvent} from './adapter';
27import {cssClasses, strings} from './constants';
28
29/**
30 * @extends {MDCFoundation<!MDCIconToggleAdapter>}
31 */
32class MDCIconToggleFoundation extends MDCFoundation {
33 static get cssClasses() {
34 return cssClasses;
35 }
36
37 static get strings() {
38 return strings;
39 }
40
41 static get defaultAdapter() {
42 return {
43 addClass: (/* className: string */) => {},
44 removeClass: (/* className: string */) => {},
45 registerInteractionHandler: (/* type: string, handler: EventListener */) => {},
46 deregisterInteractionHandler: (/* type: string, handler: EventListener */) => {},
47 setText: (/* text: string */) => {},
48 getTabIndex: () => /* number */ 0,
49 setTabIndex: (/* tabIndex: number */) => {},
50 getAttr: (/* name: string */) => /* string */ '',
51 setAttr: (/* name: string, value: string */) => {},
52 rmAttr: (/* name: string */) => {},
53 notifyChange: (/* evtData: IconToggleEvent */) => {},
54 };
55 }
56
57 constructor(adapter) {
58 super(Object.assign(MDCIconToggleFoundation.defaultAdapter, adapter));
59
60 /** @private {boolean} */
61 this.on_ = false;
62
63 /** @private {boolean} */
64 this.disabled_ = false;
65
66 /** @private {number} */
67 this.savedTabIndex_ = -1;
68
69 /** @private {?IconToggleState} */
70 this.toggleOnData_ = null;
71
72 /** @private {?IconToggleState} */
73 this.toggleOffData_ = null;
74
75 this.clickHandler_ = /** @private {!EventListener} */ (
76 () => this.toggleFromEvt_());
77
78 /** @private {boolean} */
79 this.isHandlingKeydown_ = false;
80
81 this.keydownHandler_ = /** @private {!EventListener} */ ((/** @type {!KeyboardKey} */ evt) => {
82 if (isSpace(evt)) {
83 this.isHandlingKeydown_ = true;
84 return evt.preventDefault();
85 }
86 });
87
88 this.keyupHandler_ = /** @private {!EventListener} */ ((/** @type {!KeyboardKey} */ evt) => {
89 if (isSpace(evt)) {
90 this.isHandlingKeydown_ = false;
91 this.toggleFromEvt_();
92 }
93 });
94 }
95
96 init() {
97 this.refreshToggleData();
98 this.savedTabIndex_ = this.adapter_.getTabIndex();
99 this.adapter_.registerInteractionHandler('click', this.clickHandler_);
100 this.adapter_.registerInteractionHandler('keydown', this.keydownHandler_);
101 this.adapter_.registerInteractionHandler('keyup', this.keyupHandler_);
102 }
103
104 refreshToggleData() {
105 const {DATA_TOGGLE_ON, DATA_TOGGLE_OFF} = MDCIconToggleFoundation.strings;
106 this.toggleOnData_ = this.parseJsonDataAttr_(DATA_TOGGLE_ON);
107 this.toggleOffData_ = this.parseJsonDataAttr_(DATA_TOGGLE_OFF);
108 }
109
110 destroy() {
111 this.adapter_.deregisterInteractionHandler('click', this.clickHandler_);
112 this.adapter_.deregisterInteractionHandler('keydown', this.keydownHandler_);
113 this.adapter_.deregisterInteractionHandler('keyup', this.keyupHandler_);
114 }
115
116 /** @private */
117 toggleFromEvt_() {
118 this.toggle();
119 const {on_: isOn} = this;
120 this.adapter_.notifyChange(/** @type {!IconToggleEvent} */ ({isOn}));
121 }
122
123 /** @return {boolean} */
124 isOn() {
125 return this.on_;
126 }
127
128 /** @param {boolean=} isOn */
129 toggle(isOn = !this.on_) {
130 this.on_ = isOn;
131
132 const {ARIA_LABEL, ARIA_PRESSED} = MDCIconToggleFoundation.strings;
133
134 if (this.on_) {
135 this.adapter_.setAttr(ARIA_PRESSED, 'true');
136 } else {
137 this.adapter_.setAttr(ARIA_PRESSED, 'false');
138 }
139
140 const {cssClass: classToRemove} =
141 this.on_ ? this.toggleOffData_ : this.toggleOnData_;
142
143 if (classToRemove) {
144 this.adapter_.removeClass(classToRemove);
145 }
146
147 const {content, label, cssClass} = this.on_ ? this.toggleOnData_ : this.toggleOffData_;
148
149 if (cssClass) {
150 this.adapter_.addClass(cssClass);
151 }
152 if (content) {
153 this.adapter_.setText(content);
154 }
155 if (label) {
156 this.adapter_.setAttr(ARIA_LABEL, label);
157 }
158 }
159
160 /**
161 * @param {string} dataAttr
162 * @return {!IconToggleState}
163 */
164 parseJsonDataAttr_(dataAttr) {
165 const val = this.adapter_.getAttr(dataAttr);
166 if (!val) {
167 return {};
168 }
169 return /** @type {!IconToggleState} */ (JSON.parse(val));
170 }
171
172 /** @return {boolean} */
173 isDisabled() {
174 return this.disabled_;
175 }
176
177 /** @param {boolean} isDisabled */
178 setDisabled(isDisabled) {
179 this.disabled_ = isDisabled;
180
181 const {DISABLED} = MDCIconToggleFoundation.cssClasses;
182 const {ARIA_DISABLED} = MDCIconToggleFoundation.strings;
183
184 if (this.disabled_) {
185 this.savedTabIndex_ = this.adapter_.getTabIndex();
186 this.adapter_.setTabIndex(-1);
187 this.adapter_.setAttr(ARIA_DISABLED, 'true');
188 this.adapter_.addClass(DISABLED);
189 } else {
190 this.adapter_.setTabIndex(this.savedTabIndex_);
191 this.adapter_.rmAttr(ARIA_DISABLED);
192 this.adapter_.removeClass(DISABLED);
193 }
194 }
195
196 /** @return {boolean} */
197 isKeyboardActivated() {
198 return this.isHandlingKeydown_;
199 }
200}
201
202/**
203 * @typedef {{
204 * key: string,
205 * keyCode: number
206 * }}
207 */
208let KeyboardKey;
209
210/**
211 * @param {!KeyboardKey} keyboardKey
212 * @return {boolean}
213 */
214function isSpace(keyboardKey) {
215 return keyboardKey.key === 'Space' || keyboardKey.keyCode === 32;
216}
217
218
219/** @record */
220class IconToggleState {}
221
222/**
223 * The aria-label value of the icon toggle, or undefined if there is no aria-label.
224 * @export {string|undefined}
225 */
226IconToggleState.prototype.label;
227
228/**
229 * The text for the icon toggle, or undefined if there is no text.
230 * @export {string|undefined}
231 */
232IconToggleState.prototype.content;
233
234/**
235 * The CSS class to add to the icon toggle, or undefined if there is no CSS class.
236 * @export {string|undefined}
237 */
238IconToggleState.prototype.cssClass;
239
240export default MDCIconToggleFoundation;