(js,css) Adapt time picker to latest md changes

pull/207/head
Francis Lachapelle 2016-05-09 13:52:52 -04:00
parent 4a0d4eb343
commit d8ebd0bccf
4 changed files with 135 additions and 69 deletions

5
NEWS
View File

@ -21,6 +21,8 @@ Enhancements
- [web] style transparent events (show time as free) (#3192)
Bug fixes
- [core] properly escape wide characters (#3616)
- [core] avoid double-appending domains in cache for multi-domain configurations (#3614)
- [web] fixed missing columns in SELECT statements (PostgreSQL)
- [web] fixed display of ghosts when dragging events
- [web] fixed management of mail labels in Preferences module
@ -44,8 +46,7 @@ Bug fixes
- [web] fixed menu content visibility when printing an email (#3584)
- [web] retired CSS reset so the style of HTML messages is respected (#3582)
- [web] fixed messages archiving as zip file
- [core] properly escape wide characters (#3616)
- [core] avoid double-appending domains in cache for multi-domain configurations (#3614)
- [web] adapted time picker to match changes of md calendar picker
- [dav] we now handle the default classifications for tasks (#3541)
- [eas] properly unfold long mail headers (#3152)
- [eas] correctly set EAS message class for S/MIME messages (#3576)

View File

@ -10,42 +10,54 @@
template: [
'<div class="sg-time-pane">',
' <div class="hours-pane">',
' <div ng-repeat="hoursBigLine in hours" layout="row" layout-sm="column">',
' <div ng-repeat="hoursBigLine in hours" layout="row" layout-xs="column">',
' <div ng-repeat="hoursLine in hoursBigLine" layout="row" class="hours">',
' <md-button class="hourBtn md-fab md-mini" ng-repeat="hour in hoursLine" id="{{hour.id}}"',
' <md-button class="hourBtn sg-time-selection-indicator" id="{{hour.id}}"',
' md-no-ink',
' ng-repeat="hour in hoursLine"',
' ng-click="hourClickHandler(hour.displayName)">{{hour.displayName}}</md-button>',
' </div>',
' </div>',
' </div>',
' <div class="min5" ng-show="is5min()">',
' <div layout="row" layout-sm="column">',
' <div layout="row" layout-xs="column">',
' <div ng-repeat="minutesLine in min5" layout="row">',
' <md-button class="minuteBtn md-fab md-mini" ng-repeat="minute in minutesLine" id="{{minute.id}}"',
' <md-button class="minuteBtn sg-time-selection-indicator" id="{{minute.id}}"',
' md-no-ink',
' ng-repeat="minute in minutesLine"',
' ng-click="minuteClickHandler(minute.displayName)">{{minute.displayName}}</md-button>',
' </div>',
' </div>',
' </div>',
' <div class="sg-time-scroll-mask" ng-hide="is5min()">',
' <div class="min1" layout="row" layout-sm="column" layout-wrap>',
' <div ng-repeat="minutesLine in min1" layout="row" layout-align="space-around center" flex="50">',
' <md-button class="minuteBtn md-fab md-mini" ng-repeat="minute in minutesLine" id="{{minute.id}}"',
' <div class="min1" layout="row" layout-xs="column" layout-wrap>',
' <div ng-repeat="minutesLine in min1" layout="row" layout-align="space-around center">',
' <md-button class="minuteBtn sg-time-selection-indicator" id="{{minute.id}}"',
' md-no-ink',
' ng-repeat="minute in minutesLine"',
' ng-click="minuteClickHandler(minute.displayName)">{{minute.displayName}}</md-button>',
' </div>',
' </div>',
' </div>',
' <div flex layout="row" layout-align="center center" class="toggle-pane">',
' <div flex layout="row" layout-align="center center" md-colors="::{background: \'default-background-200\'}">',
' <md-button class="toggleBtn md-fab md-mini" ng-bind="getToggleBtnLbl()" ng-click="toggleManual5min()"></md-button>',
' </div>',
'</div>'
].join(''),
scope: {},
require: ['ngModel', 'sgTimePane'],
require: ['ngModel', 'sgTimePane', '?^mdInputContainer'],
controller: TimePaneCtrl,
controllerAs: 'ctrl',
bindToController: true,
link: function(scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var sgTimePaneCtrl = controllers[1];
var mdInputContainer = controllers[2];
if (mdInputContainer) {
throw Error('sg-timepicker should not be placed inside md-input-container.');
}
var timePaneElement = element;
sgTimePaneCtrl.configureNgModel(ngModelCtrl, sgTimePaneCtrl, timePaneElement);
}
@ -53,7 +65,7 @@
}
/** Class applied to the selected hour or minute cell/. */
var SELECTED_TIME_CLASS = 'md-bg';
var SELECTED_TIME_CLASS = 'sg-time-selected';
/** Class applied to the focused hour or minute cell/. */
var FOCUSED_TIME_CLASS = 'md-focus';
@ -359,7 +371,7 @@
'<md-button class="sg-timepicker-button md-icon-button" type="button" ',
' tabindex="-1" aria-hidden="true" ',
' ng-click="ctrl.openTimePane($event)">',
' <md-icon>access_time</md-icon>',
' <md-icon class="sg-timepicker-icon">access_time</md-icon>',
'</md-button>',
'<div class="md-default-theme sg-timepicker-input-container" ',
' ng-class="{\'sg-timepicker-focused\': ctrl.isFocused,',
@ -376,10 +388,10 @@
// This pane will be detached from here and re-attached to the document body.
'<div class="sg-timepicker-time-pane md-whiteframe-z1">',
' <div class="sg-timepicker-input-mask">',
' <div class="sg-timepicker-input-mask-opaque',
' md-default-theme md-background md-bg"></div>', // using mdColors
' <div class="sg-timepicker-input-mask-opaque"',
' md-colors="::{background: \'default-background-A100\'}"></div>', // using mdColors
' </div>',
' <div class="sg-timepicker-time md-default-theme md-bg md-background">',
' <div class="sg-timepicker-time" md-colors="::{background: \'default-background-A100\'}">',
' <sg-time-pane role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ',
' ng-model="ctrl.time" ng-if="ctrl.isTimeOpen"></sg-time-pane>',
' </div>',
@ -418,8 +430,8 @@
* This is computed statically now, but can be changed to be measured if the circumstances
* of calendar sizing are changed.
*/
var TIME_PANE_HEIGHT = { MIN5: { GTSM: 172 + 20, SM: 292 + 20 },
MIN1: { GTSM: 364 + 20, SM: 454 + 20 } };
var TIME_PANE_HEIGHT = { MIN5: { GTXS: 172 + 20, XS: 291 + 20 },
MIN1: { GTXS: 364 + 20, XS: 454 + 20 } };
/**
* Width of the calendar pane used to check if the pane is going outside the boundary of
@ -429,7 +441,7 @@
* This is computed statically now, but can be changed to be measured if the circumstances
* of calendar sizing are changed.
*/
var TIME_PANE_WIDTH = { GTSM: 510 + 20, SM: 272 + 20 };
var TIME_PANE_WIDTH = { GTXS: 510 + 20, XS: 274 + 20 };
/**
* Controller for sg-timepicker.
@ -624,7 +636,7 @@
if (this.$attrs.ngDisabled) {
// The expression is to be evaluated against the directive element's scope and not
// the directive's isolate scope.
var scope = this.$mdUtil.validateScope(this.$element) ? this.$element.scope() : null;
var scope = this.$scope.$parent;
if (scope) {
scope.$watch(this.$attrs.ngDisabled, function(isDisabled) {
self.setDisabled(isDisabled);
@ -731,7 +743,6 @@
TimePickerCtrl.prototype.attachTimePane = function() {
var timePane = this.timePane;
this.$element.addClass('sg-timepicker-open');
this.$element.find('button').addClass('md-primary');
var elementRect = this.inputContainer.getBoundingClientRect();
var bodyRect = document.body.getBoundingClientRect();
@ -741,26 +752,48 @@
var paneTop = elementRect.top - bodyRect.top;
var paneLeft = elementRect.left - bodyRect.left;
// If ng-material has disabled body scrolling (for example, if a dialog is open),
// then it's possible that the already-scrolled body has a negative top/left. In this case,
// we want to treat the "real" top as (0 - bodyRect.top). In a normal scrolling situation,
// though, the top of the viewport should just be the body's scroll position.
var viewportTop = (bodyRect.top < 0 && document.body.scrollTop === 0) ?
-bodyRect.top :
document.body.scrollTop;
var viewportLeft = (bodyRect.left < 0 && document.body.scrollLeft === 0) ?
-bodyRect.left :
document.body.scrollLeft;
var viewportBottom = viewportTop + this.$window.innerHeight;
var viewportRight = viewportLeft + this.$window.innerWidth;
// If the right edge of the pane would be off the screen and shifting it left by the
// difference would not go past the left edge of the screen.
var paneWidth = this.$mdMedia('sm')? TIME_PANE_WIDTH.SM : TIME_PANE_WIDTH.GTSM;
if (paneLeft + paneWidth > bodyRect.right &&
bodyRect.right - paneWidth > 0) {
paneLeft = bodyRect.right - paneWidth;
// difference would not go past the left edge of the screen. If the time pane is too
// big to fit on the screen at all, move it to the left of the screen and scale the entire
// element down to fit.
var paneWidth = this.$mdMedia('xs')? TIME_PANE_WIDTH.XS : TIME_PANE_WIDTH.GTXS;
if (paneLeft + paneWidth > viewportRight) {
if (viewportRight - paneWidth > 0) {
paneLeft = viewportRight - paneWidth;
} else {
paneLeft = viewportLeft;
var scale = this.$window.innerWidth / paneWidth;
timePane.style.transform = 'scale(' + scale + ')';
}
timePane.classList.add('sg-timepicker-pos-adjusted');
}
timePane.style.left = paneLeft + 'px';
// If the bottom edge of the pane would be off the screen and shifting it up by the
// difference would not go past the top edge of the screen.
var min = (typeof this.time == 'object' && this.time.getMinutes() % 5 === 0)? 'MIN5' : 'MIN1';
var paneHeight = this.$mdMedia('sm')? TIME_PANE_HEIGHT[min].SM : TIME_PANE_HEIGHT[min].GTSM;
if (paneTop + paneHeight > bodyRect.bottom &&
bodyRect.bottom - paneHeight > 0) {
paneTop = bodyRect.bottom - paneHeight;
var paneHeight = this.$mdMedia('xs')? TIME_PANE_HEIGHT[min].XS : TIME_PANE_HEIGHT[min].GTXS;
if (paneTop + paneHeight > viewportBottom &&
viewportBottom - paneHeight > viewportTop) {
paneTop = viewportBottom - paneHeight;
timePane.classList.add('sg-timepicker-pos-adjusted');
}
timePane.style.left = paneLeft + 'px';
timePane.style.top = paneTop + 'px';
document.body.appendChild(timePane);
@ -779,10 +812,13 @@
/** Detach the floating time pane from the document. */
TimePickerCtrl.prototype.detachTimePane = function() {
this.$element.removeClass('sg-timepicker-open');
this.$element.find('button').removeClass('md-primary');
this.timePane.classList.remove('md-pane-open');
this.timePane.classList.remove('md-timepicker-pos-adjusted');
if (this.isTimeOpen) {
this.$mdUtil.enableScrolling();
}
if (this.timePane.parentNode) {
// Use native DOM removal because we do not want any of the angular state of this element
// to be disposed.
@ -822,11 +858,12 @@
/** Close the floating time pane. */
TimePickerCtrl.prototype.closeTimePane = function() {
if (this.isTimeOpen) {
this.isTimeOpen = false;
this.detachTimePane();
this.isTimeOpen = false;
this.timePaneOpenedFrom.focus();
this.timePaneOpenedFrom = null;
this.$mdUtil.enableScrolling();
this.ngModelCtrl.$setTouched();
document.body.removeEventListener('click', this.bodyClickHandler);
window.removeEventListener('resize', this.windowResizeHandler);
@ -853,6 +890,9 @@
* @param {boolean} isFocused
*/
TimePickerCtrl.prototype.setFocused = function(isFocused) {
if (!isFocused) {
this.ngModelCtrl.$setTouched();
}
this.isFocused = isFocused;
};

View File

@ -3,17 +3,19 @@
sg-timepicker.md-THEME_NAME-theme {
background: white; }
.md-THEME_NAME-theme .sg-timepicker-input-container {
border-bottom-color: '{{background-300}}'; }
.md-THEME_NAME-theme .sg-timepicker-input-container.sg-timepicker-focused {
.md-THEME_NAME-theme {
.sg-timepicker-input-container {
border-bottom-color: '{{background-300}}'; }
.sg-timepicker-input-container.sg-timepicker-focused {
border-bottom-color: '{{primary-500}}'; }
.md-THEME_NAME-theme .sg-timepicker-input-container.sg-timepicker-invalid {
.sg-timepicker-input-container.sg-timepicker-invalid {
border-bottom-color: '{{warn-500}}'; }
.md-THEME_NAME-theme .sg-timepicker-time-pane {
.sg-timepicker-time-pane {
border-color: '{{background-300}}'; }
.md-THEME_NAME-theme .sg-timepicker-triangle-button:hover .sg-timepicker-expand-triangle {
.sg-timepicker-triangle-button:hover .sg-timepicker-expand-triangle {
border-top-color: '{{foreground-2}}'; }
.md-THEME_NAME-theme .sg-timepicker-open .sg-timepicker-time-icon {
.sg-timepicker-open .sg-timepicker-time-icon {
fill: '{{primary-500}}'; }
.md-THEME_NAME-theme .sg-timepicker-calendar {
.sg-timepicker-calendar {
background: white; }
}

View File

@ -1,9 +1,11 @@
/** Styles for sgTimePane. */
$sg-time-pane-cell-size: 40px;
$sg-time-width: (12 * $sg-time-pane-cell-size) + (2 * $md-calendar-side-padding);
$sg-time-font-size: 13px;
sg-time-pane {
font-size: 13px;
.sg-time-pane {
font-size: $sg-time-font-size;
user-select: none;
}
@ -12,11 +14,6 @@ sg-time-pane {
border-bottom: solid 1px rgb(224,224,224);
}
.toggle-pane {
// TODO: should use background-200
border-top: solid 1px rgb(224,224,224);
}
.sg-time-scroll-mask {
display: inline-block;
overflow: hidden;
@ -39,35 +36,53 @@ sg-time-pane {
padding: 0 $md-calendar-side-padding;
}
.md-button.md-fab.hourBtn,
.md-button.md-fab.minuteBtn,
// Circle element inside of every hour/minute cell used to indicate selection or focus.
.sg-time-selection-indicator {
transition: background-color, color $swift-ease-out-duration $swift-ease-out-timing-function;
border-radius: 50%;
display: inline-block;
font-size: $sg-time-font-size;
font-weight: normal;
width: $md-calendar-cell-emphasis-size;
min-width: $md-calendar-cell-emphasis-size;
height: $md-calendar-cell-emphasis-size;
line-height: $md-calendar-cell-emphasis-size;
margin: 0;
// .md-calendar-date:not(.md-disabled) & {
// cursor: pointer;
// }
&:hover {
background: $colorGrey300; // {{background-300}}
}
&.md-focus {
background: $colorGrey200; // {{background-hue-1}}
}
&.sg-time-selected, &:hover.sg-time-selected, &.md-focus.sg-time-selected {
background: sg-color($sogoBlue, 500); // {{primary-500}}
color: #fff; // {{primary-500-contrast}}
border-color: transparent;
}
}
.md-button.md-fab.toggleBtn,
.md-button.md-fab.hourBtn.md-focused,
.md-button.md-fab.minuteBtn.md-focused,
.md-button.md-fab.toggleBtn.md-focused,
.md-button.md-fab.hourBtn.md-focus,
.md-button.md-fab.minuteBtn.md-focus,
.md-button.md-fab.toggleBtn.md-focus {
min-width: 10px;
min-height: 10px;
border-color: transparent;
font-weight:normal;
color: rgba(0,0,0,0.5);
color: #fff;
height: $sg-time-pane-cell-size;
width: $sg-time-pane-cell-size;
line-height: $sg-time-pane-cell-size;
box-shadow: none;
margin: 0;
&:not(.md-bg):not(.toggleBtn) {
background-color: transparent;
&:hover {
background-color: lightgrey;
color: #666666;
}
}
}
.md-button.md-fab.toggleBtn {
color: white;
margin: 5px;
}
@ -189,18 +204,26 @@ sg-timepicker[disabled] {
// Open state for all of the elements of the picker.
.sg-timepicker-open {
.sg-timepicker-input-container {
margin-left: -$md-datepicker-button-gap;
@include rtl-prop(margin-left, margin-right, -$md-datepicker-button-gap);
// The negative bottom margin prevents the content around the datepicker
// from jumping when it gets opened.
margin-bottom: -$md-datepicker-border-bottom-gap;
border: none;
}
.sg-timepicker-input {
margin-left: 24px;
@include rtl-prop(margin-left, margin-right, 24px);
height: 40px;
}
.sg-timepicker-triangle-button {
display: none;
}
.sg-timepicker-icon {
color: sg-color($sogoBlue, 500); //'{{primary-500}}';
}
}
// When the position of the floating calendar pane is adjusted to remain inside