UNPKG

8.78 kBJavaScriptView Raw
1/*
2Copyright 2013-2015 ASIAL CORPORATION
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15
16*/
17
18import onsElements from '../ons/elements.js';
19import util from '../ons/util.js';
20import autoStyle from '../ons/autostyle.js';
21import ModifierUtil from '../ons/internal/modifier-util.js';
22import BaseCheckboxElement from './base/base-checkbox.js';
23import contentReady from '../ons/content-ready.js';
24import GestureDetector from '../ons/gesture-detector.js';
25
26const scheme = {
27 '': 'switch--*',
28 '.switch__input': 'switch--*__input',
29 '.switch__handle': 'switch--*__handle',
30 '.switch__toggle': 'switch--*__toggle'
31};
32
33const locations = {
34 ios: [1, 21],
35 material: [0, 16]
36};
37
38/**
39 * @element ons-switch
40 * @category form
41 * @description
42 * [en]
43 * Switch component. The switch can be toggled both by dragging and tapping.
44 *
45 * Will automatically displays a Material Design switch on Android devices.
46 * [/en]
47 * [ja]スイッチを表示するコンポーネントです。[/ja]
48 * @modifier material
49 * [en]Material Design switch[/en]
50 * [ja][/ja]
51 * @codepen LpXZQQ
52 * @tutorial vanilla/Reference/switch
53 * @guide theming.html#modifiers [en]More details about the `modifier` attribute[/en][ja]modifier属性の使い方[/ja]
54 * @example
55 * <ons-switch checked></ons-switch>
56 * <ons-switch disabled></ons-switch>
57 * <ons-switch modifier="material"></ons-switch>
58 */
59
60export default class SwitchElement extends BaseCheckboxElement {
61
62 constructor() {
63 super();
64
65 contentReady(this, () => {
66 this.attributeChangedCallback('modifier', null, this.getAttribute('modifier'));
67 });
68
69 this._onChange = this._onChange.bind(this);
70 this._onRelease = this._onRelease.bind(this);
71 this._lastTimeStamp = 0;
72 }
73
74 get _scheme() {
75 return scheme;
76 }
77
78 get _defaultClassName() {
79 return 'switch';
80 }
81
82 get _template() {
83 return `
84 <input type="${this.type}" class="${this._defaultClassName}__input">
85 <div class="${this._defaultClassName}__toggle">
86 <div class="${this._defaultClassName}__handle">
87 <div class="${this._defaultClassName}__touch"></div>
88 </div>
89 </div>
90 `;
91 }
92
93 get type() {
94 return 'checkbox';
95 }
96
97 /* Own props */
98
99 _getPosition(e) {
100 const l = this._locations;
101 return Math.min(l[1], Math.max(l[0], this._startX + e.gesture.deltaX));
102 }
103
104 _emitChangeEvent() {
105 util.triggerElementEvent(this, 'change', {
106 value: this.checked,
107 switch: this,
108 isInteractive: true
109 });
110 }
111
112 _onChange(event) {
113 if (event && event.stopPropagation) {
114 event.stopPropagation();
115 }
116
117 this._emitChangeEvent();
118 }
119
120 _onClick(ev) {
121 if (ev.target.classList.contains(`${this.defaultElementClass}__touch`)
122 || (ev.timeStamp - this._lastTimeStamp < 50) // Prevent second click triggered by <label>
123 ) {
124 ev.preventDefault();
125 }
126 this._lastTimeStamp = ev.timeStamp;
127 }
128
129 _onHold(e) {
130 if (!this.disabled) {
131 ModifierUtil.addModifier(this, 'active');
132 document.addEventListener('release', this._onRelease);
133 }
134 }
135
136 _onDragStart(e) {
137 if (this.disabled || ['left', 'right'].indexOf(e.gesture.direction) === -1) {
138 ModifierUtil.removeModifier(this, 'active');
139 return;
140 }
141
142 e.consumed = true;
143
144 ModifierUtil.addModifier(this, 'active');
145 this._startX = this._locations[this.checked ? 1 : 0];// - e.gesture.deltaX;
146
147 this.addEventListener('drag', this._onDrag);
148 document.addEventListener('release', this._onRelease);
149 }
150
151 _onDrag(e) {
152 e.stopPropagation();
153 this._handle.style.left = this._getPosition(e) + 'px';
154 }
155
156 _onRelease(e) {
157 const l = this._locations;
158 const position = this._getPosition(e);
159 const previousValue = this.checked;
160
161 this.checked = position >= (l[0] + l[1]) / 2;
162
163 if (this.checked !== previousValue) {
164 this._emitChangeEvent();
165 }
166
167 this.removeEventListener('drag', this._onDrag);
168 document.removeEventListener('release', this._onRelease);
169
170 this._handle.style.left = '';
171 ModifierUtil.removeModifier(this, 'active');
172 }
173
174 click(ev = {}) {
175 if (!this.disabled) {
176 this.checked = !this.checked;
177 this._emitChangeEvent();
178 this._lastTimeStamp = ev.timeStamp || 0;
179 }
180 }
181
182 get _handle() {
183 return this.querySelector(`.${this._defaultClassName}__handle`);
184 }
185
186 get checkbox() {
187 return this._input;
188 }
189
190 connectedCallback() {
191 contentReady(this, () => {
192 this._input.addEventListener('change', this._onChange);
193 });
194
195 this.addEventListener('dragstart', this._onDragStart);
196 this.addEventListener('hold', this._onHold);
197 this.addEventListener('tap', this.click);
198 this.addEventListener('click', this._onClick);
199 this._gestureDetector = new GestureDetector(this, { dragMinDistance: 1, holdTimeout: 251, passive: true });
200 }
201
202 disconnectedCallback() {
203 contentReady(this, () => {
204 this._input.removeEventListener('change', this._onChange);
205 });
206
207 this.removeEventListener('dragstart', this._onDragStart);
208 this.removeEventListener('hold', this._onHold);
209 this.removeEventListener('tap', this.click);
210 this.removeEventListener('click', this._onClick);
211 if (this._gestureDetector) {
212 this._gestureDetector.dispose();
213 }
214 }
215
216 static get observedAttributes() {
217 return [...super.observedAttributes, 'modifier'];
218 }
219
220 attributeChangedCallback(name, last, current) {
221 if (name === 'modifier') {
222 const md = (current || '').indexOf('material') !== -1;
223 this._locations = locations[md ? 'material' : 'ios'];
224 }
225
226 super.attributeChangedCallback(name, last, current);
227 }
228
229 /**
230 * @event change
231 * @description
232 * [en]Fired when the switch is toggled.[/en]
233 * [ja]ON/OFFが変わった時に発火します。[/ja]
234 * @param {Object} event
235 * [en]Event object.[/en]
236 * [ja]イベントオブジェクト。[/ja]
237 * @param {Object} event.switch
238 * [en]Switch object.[/en]
239 * [ja]イベントが発火したSwitchオブジェクトを返します。[/ja]
240 * @param {Boolean} event.value
241 * [en]Current value.[/en]
242 * [ja]現在の値を返します。[/ja]
243 * @param {Boolean} event.isInteractive
244 * [en]True if the change was triggered by the user clicking on the switch.[/en]
245 * [ja]タップやクリックなどのユーザの操作によって変わった場合にはtrueを返します。[/ja]
246 */
247
248 /**
249 * @attribute modifier
250 * @type {String}
251 * @description
252 * [en]The appearance of the switch.[/en]
253 * [ja]スイッチの表現を指定します。[/ja]
254 */
255
256 /**
257 * @attribute disabled
258 * @description
259 * [en]Whether the switch is be disabled.[/en]
260 * [ja]スイッチを無効の状態にする場合に指定します。[/ja]
261 */
262
263 /**
264 * @attribute checked
265 * @description
266 * [en]Whether the switch is checked.[/en]
267 * [ja]スイッチがONの状態にするときに指定します。[/ja]
268 */
269
270 /**
271 * @attribute input-id
272 * @type {String}
273 * @description
274 * [en]Specify the `id` attribute of the inner `<input>` element. This is useful when using `<label for="...">` elements.[/en]
275 * [ja][/ja]
276 */
277
278 /**
279 * @property checked
280 * @type {Boolean}
281 * @description
282 * [en]This value is `true` if the switch is checked.[/en]
283 * [ja]スイッチがONの場合に`true`。[/ja]
284 */
285
286 /**
287 * @property value
288 * @type {String}
289 * @description
290 * [en]The current value of the input.[/en]
291 * [ja][/ja]
292 */
293
294 /**
295 * @property disabled
296 * @type {Boolean}
297 * @description
298 * [en]Whether the element is disabled or not.[/en]
299 * [ja]無効化されている場合に`true`。[/ja]
300 */
301
302 /**
303 * @property checkbox
304 * @readonly
305 * @type {HTMLElement}
306 * @description
307 * [en]The underlying checkbox element.[/en]
308 * [ja]コンポーネント内部のcheckbox要素になります。[/ja]
309 */
310
311 /**
312 * @method focus
313 * @signature focus()
314 * @description
315 * [en]Focuses the switch.[/en]
316 * [ja][/ja]
317 */
318
319 /**
320 * @method blur
321 * @signature blur()
322 * @description
323 * [en]Removes focus from the switch.[/en]
324 * [ja][/ja]
325 */
326}
327
328onsElements.Switch = SwitchElement;
329customElements.define('ons-switch', SwitchElement);