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

Commit 1f0d1c6

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

File tree

6 files changed

+265
-76
lines changed

6 files changed

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

0 commit comments

Comments
 (0)