Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit f9ad10c

Browse files
committed
refactor(switch): refactor for spec changes
1 parent 2ca21f8 commit f9ad10c

File tree

9 files changed

+285
-80
lines changed

9 files changed

+285
-80
lines changed

src/components/checkbox/checkbox.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ angular.module('material.components.checkbox', [
4848
*/
4949
function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant, $mdTheming) {
5050
inputDirective = inputDirective[0];
51-
5251
var CHECKED_CSS = 'md-checked';
5352

5453
return {
@@ -77,15 +76,6 @@ function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant,
7776
var checked = false;
7877
$mdTheming(element);
7978

80-
// Create a mock ngModel if the user doesn't provide one
81-
ngModelCtrl = ngModelCtrl || {
82-
$setViewValue: function(value) {
83-
this.$viewValue = value;
84-
},
85-
$parsers: [],
86-
$formatters: []
87-
};
88-
8979
$mdAria.expectWithText(tElement, 'aria-label');
9080

9181
// Reuse the original input[type=checkbox] directive from Angular core.
@@ -96,7 +86,11 @@ function MdCheckboxDirective(inputDirective, $mdInkRipple, $mdAria, $mdConstant,
9686
0: {}
9787
}, attr, [ngModelCtrl]);
9888

99-
element.on('click', listener);
89+
// Used by switch. in Switch, we don't want click listeners; we have more granular
90+
// touchup/touchdown listening.
91+
if (!attr.noClick) {
92+
element.on('click', listener);
93+
}
10094
element.on('keypress', keypressHandler);
10195
ngModelCtrl.$render = render;
10296

src/components/switch/_switch.scss

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,77 @@
1-
$switch-width: $baseline-grid * 8;
1+
$switch-width: 36px !default;
2+
$switch-height: $baseline-grid * 3 !default;
3+
$switch-bar-height: 14px !default;
4+
$switch-thumb-size: 20px !default;
25

36
md-switch {
4-
display: block;
5-
position: relative;
6-
height: $baseline-grid * 3;
7-
margin: $baseline-grid;
87

98
display: flex;
109
align-items: center;
1110

12-
.md-switch-bar {
11+
.md-container {
12+
width: $switch-width;
13+
height: $switch-height;
14+
position: relative;
15+
user-select: none;
16+
margin-right: 8px;
17+
}
18+
19+
.md-bar {
20+
left: 1px;
21+
width: $switch-width - 2px;
22+
top: $switch-height / 2 - $switch-bar-height / 2;
23+
height: $switch-bar-height;
24+
border-radius: 8px;
1325
position: absolute;
14-
left: $baseline-grid * 2;
15-
top: $baseline-grid * 1.5;
16-
width: $baseline-grid * 4;
17-
height: 1px;
18-
pointer-events: none;
1926
}
2027

21-
/* used also in _radio-button.scss */
22-
.md-switch-thumb {
28+
.md-thumb-container {
29+
top: $switch-height / 2 - $switch-thumb-size / 2;
30+
left: 0;
31+
width: $switch-width - $switch-thumb-size;
32+
position: absolute;
33+
transform: translate3d(0,0,0);
34+
z-index: 1;
35+
}
36+
&.md-checked .md-thumb-container {
37+
transform: translate3d(100%,0,0);
38+
}
39+
40+
.md-thumb {
2341
position: absolute;
2442
margin: 0;
2543
left: 0;
2644
top: 0;
2745
outline: none;
46+
height: $switch-thumb-size;
47+
width: $switch-thumb-size;
48+
border-radius: 50%;
49+
box-shadow: $whiteframe-shadow-z1;
2850

29-
.md-container {
51+
.md-ripple-container {
3052
position: absolute;
31-
transition: transform 0.2s linear;
32-
transform: translate3d(0,0,0);
33-
}
34-
&.md-checked .md-container {
35-
transform: translate3d($switch-width - 16,0,0);
53+
display: block;
54+
width: auto;
55+
height: auto;
56+
left: -$switch-thumb-size;
57+
top: -$switch-thumb-size;
58+
right: -$switch-thumb-size;
59+
bottom: -$switch-thumb-size;
60+
z-index: 0;
3661
}
62+
}
3763

38-
.md-label {
39-
margin-left: $baseline-grid * 9;
64+
&.transition {
65+
.md-bar,
66+
.md-thumb-container,
67+
.md-thumb {
68+
transition: $swift-ease-in-out;
69+
transition-property: transform, background-color;
70+
}
71+
.md-bar,
72+
.md-thumb {
73+
transition-delay: 0.05s;
4074
}
4175
}
76+
4277
}

src/components/switch/demoBasicUsage/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="inset">
1+
<div class="inset" ng-controller="SwitchDemoCtrl">
22
<md-switch ng-model="data.cb1" aria-label="Switch 1">
33
Switch 1: {{ data.cb1 }}
44
</md-switch>
@@ -11,7 +11,7 @@
1111
Switch (Disabled)
1212
</md-switch>
1313

14-
<md-switch ng-disabled="true" aria-label="Disabled active switch" ng-model="data.cb4" ng-init="data.cb4=true">
14+
<md-switch ng-disabled="true" aria-label="Disabled active switch" ng-model="data.cb4">
1515
Switch (Disabled, Active)
1616
</md-switch>
1717

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
angular.module('switchDemo1', ['ngMaterial']);
1+
angular.module('switchDemo1', ['ngMaterial'])
2+
.controller('SwitchDemoCtrl', function($scope) {
3+
$scope.data = {
4+
cb1: true,
5+
cb2: 'nope',
6+
cb4: true
7+
};
8+
});
Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
1-
$switch-color: $foreground-secondary-color !default;
2-
$switch-focus-color: map-get($foreground-color-palette, '1000');
1+
$switch-color-palette: $primary-color-palette !default;
2+
$switch-off-color-palette: $foreground-color-palette !default;
3+
4+
$switch-on-color: map-get($switch-color-palette, '500') !default;
5+
$switch-on-bar-color: rgba($switch-on-color, 0.5) !default;
6+
7+
$switch-off-color: map-get($switch-off-color-palette, '50') !default;
8+
$switch-off-bar-color: map-get($switch-off-color-palette, '500') !default;
9+
10+
$switch-disabled-color: map-get($switch-off-color-palette, '400') !default;
11+
$switch-disabled-bar-color: rgba(#000, 0.12);
312

413
md-switch.md-#{$theme-name}-theme {
5-
.md-switch-bar {
6-
background-color: $switch-color;
14+
.md-thumb {
15+
background-color: $switch-off-color;
16+
}
17+
.md-bar {
18+
background-color: $switch-off-bar-color;
19+
}
20+
21+
&.md-checked {
22+
.md-thumb {
23+
background-color: $switch-on-color;
24+
}
25+
.md-bar {
26+
background-color: $switch-on-bar-color;
27+
}
728
}
8-
.md-switch-thumb {
9-
&:focus .md-label {
10-
border: 1px dotted black;
29+
30+
&[disabled] {
31+
.md-thumb {
32+
background-color: $switch-disabled-color;
33+
}
34+
.md-bar {
35+
background-color: $switch-disabled-bar-color;
1136
}
1237
}
1338
}

src/components/switch/switch.js

Lines changed: 148 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99

1010
angular.module('material.components.switch', [
1111
'material.core',
12-
'material.components.checkbox',
13-
'material.components.radioButton'
12+
'material.components.checkbox'
1413
])
1514
.directive('mdSwitch', MdSwitch);
1615

@@ -47,30 +46,167 @@ angular.module('material.components.switch', [
4746
*
4847
* </hljs>
4948
*/
50-
function MdSwitch(mdCheckboxDirective, mdRadioButtonDirective, $mdTheming) {
49+
function MdSwitch(mdCheckboxDirective, $mdTheming, $mdUtil, $document, $mdConstant, $parse, $$rAF) {
5150
var checkboxDirective = mdCheckboxDirective[0];
52-
var radioButtonDirective = mdRadioButtonDirective[0];
5351

5452
return {
5553
restrict: 'E',
5654
transclude: true,
5755
template:
58-
'<div class="md-switch-bar"></div>' +
59-
'<div class="md-switch-thumb">' +
60-
radioButtonDirective.template +
56+
'<div class="md-container">' +
57+
'<div class="md-bar"></div>' +
58+
'<div class="md-thumb-container">' +
59+
'<div class="md-thumb" md-ink-ripple md-ink-ripple-checkbox></div>' +
60+
'</div>'+
61+
'</div>' +
62+
'<div class="md-text" ng-transclude>' +
6163
'</div>',
6264
require: '?ngModel',
6365
compile: compile
6466
};
6567

6668
function compile(element, attr) {
67-
var thumb = angular.element(element[0].querySelector('.md-switch-thumb'));
68-
var checkboxLink = checkboxDirective.compile(thumb, attr);
69+
var checkboxLink = checkboxDirective.compile(element, attr);
6970

70-
return function (scope, element, attr, ngModelCtrl) {
71-
$mdTheming(element);
72-
return checkboxLink(scope, thumb, attr, ngModelCtrl);
71+
return function (scope, element, attr, ngModel) {
72+
ngModel = ngModel || $mdUtil.fakeNgModel();
73+
var disabledGetter = $parse(attr.ngDisabled);
74+
var thumbContainer = angular.element(element[0].querySelector('.md-thumb-container'));
75+
var elementWidth;
76+
77+
// no transition on initial load
78+
$$rAF(function() {
79+
element.addClass('transition');
80+
});
81+
82+
// Tell the checkbox we don't want a click listener.
83+
// Our drag listener tells us everything, using more granular events.
84+
attr.noClick = true;
85+
checkboxLink(scope, element, attr, ngModel);
86+
87+
setupDrag(element, {
88+
onDragStart: onDragStart,
89+
onDrag: onDrag,
90+
onDragEnd: onDragEnd
91+
});
92+
93+
function onDragStart(ev, drag) {
94+
// Don't go if ng-disabled===true
95+
if (disabledGetter(scope)) return false;
96+
elementWidth = thumbContainer.prop('offsetWidth');
97+
element.removeClass('transition');
98+
}
99+
function onDrag(ev, drag) {
100+
var percent = drag.distance / elementWidth;
101+
102+
var translate = ngModel.$viewValue ?
103+
1 - percent : //if checked, start from right
104+
-percent; // else, start from left
105+
translate = Math.max(0, Math.min(1, translate));
106+
107+
thumbContainer.css($mdConstant.CSS.TRANSFORM, 'translate3d(' + (100*translate) + '%,0,0)');
108+
drag.translate = translate;
109+
}
110+
function onDragEnd(ev, drag) {
111+
if (disabledGetter(scope)) return false;
112+
113+
element.addClass('transition');
114+
thumbContainer.css($mdConstant.CSS.TRANSFORM, '');
115+
116+
// We changed if there is no distance (this is a click a click),
117+
// or if the drag distance is >50% of the total.
118+
var isChanged = Math.abs(drag.distance) < 5 ||
119+
(ngModel.$viewValue ? drag.translate < 0.5 : drag.translate > 0.5);
120+
if (isChanged) {
121+
scope.$apply(function() {
122+
ngModel.$setViewValue(!ngModel.$viewValue);
123+
ngModel.$render();
124+
});
125+
}
126+
}
73127
};
74128
}
129+
130+
function setupDrag(element, options) {
131+
// The state of the current drag
132+
var drag;
133+
// Whether the pointer is currently down on this element.
134+
var pointerIsDown;
135+
136+
var START_EVENTS = 'mousedown touchstart pointerdown';
137+
var MOVE_EVENTS = 'mousemove touchmove pointermove';
138+
var END_EVENTS = 'mouseup mouseleave touchend touchcancel pointerup pointercancel';
139+
140+
// TODO implement vertical/horizontal drag if needed
141+
options = angular.extend({
142+
onDragStart: angular.noop,
143+
onDrag: angular.noop,
144+
onDragEnd: angular.noop
145+
}, options);
146+
147+
element.on(START_EVENTS, startDrag);
148+
149+
// Listen to move and end events on document. End events especially could have bubbled up
150+
// from the child.
151+
$document.on(MOVE_EVENTS, doDrag)
152+
.on(END_EVENTS, endDrag);
153+
154+
element.on('$destroy', function() {
155+
$document.off(MOVE_EVENTS, doDrag)
156+
.off(END_EVENTS, endDrag);
157+
});
158+
159+
function startDrag(ev) {
160+
if (pointerIsDown) return;
161+
pointerIsDown = true;
162+
163+
drag = {
164+
// Restrict this drag to whatever started it: if a mousedown started the drag,
165+
// don't let anything but mouse events continue it.
166+
pointerType: ev.type.charAt(0),
167+
startX: getPosition(ev),
168+
startTime: $mdUtil.now()
169+
};
170+
// Allow user to cancel by returning false
171+
if (options.onDragStart(ev, drag) === false) {
172+
drag = null;
173+
}
174+
}
175+
function doDrag(ev) {
176+
if (!drag || !isProperEventType(ev)) return;
177+
178+
updateDrag(ev);
179+
180+
// Allow user to cancel by returning false
181+
if (options.onDrag(ev, drag) === false) {
182+
endDrag(ev);
183+
}
184+
}
185+
function endDrag(ev) {
186+
pointerIsDown = false;
187+
if (!drag || !isProperEventType(ev)) return;
188+
189+
updateDrag(ev);
190+
options.onDragEnd(ev, drag);
191+
drag = null;
192+
}
193+
194+
function updateDrag(ev) {
195+
var x = getPosition(ev);
196+
drag.distance = drag.startX - x;
197+
drag.direction = drag.distance > 0 ? 'left' : (drag.distance < 0 ? 'right' : '');
198+
drag.time = drag.startTime - $mdUtil.now();
199+
drag.velocity = Math.abs(drag.distance) / drag.time;
200+
}
201+
function getPosition(ev) {
202+
ev = ev.originalEvent || ev; //suport jQuery events
203+
return (ev.touches ? ev.touches[0] : ev).pageX;
204+
}
205+
function isProperEventType(ev) {
206+
return drag && ev && (ev.type || '').charAt(0) == drag.pointerType;
207+
}
208+
}
209+
75210
}
211+
76212
})();

0 commit comments

Comments
 (0)