From d8ebd0bccfc66ff468e997118f0748322a5eb74a Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Mon, 9 May 2016 13:52:52 -0400 Subject: [PATCH] (js,css) Adapt time picker to latest md changes --- NEWS | 5 +- .../js/Common/sgTimepicker.directive.js | 104 ++++++++++++------ .../timepicker/timepicker-default-theme.css | 18 +-- .../components/timepicker/timepicker.scss | 77 ++++++++----- 4 files changed, 135 insertions(+), 69 deletions(-) diff --git a/NEWS b/NEWS index 5ecfb6267..8b99eebfe 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/UI/WebServerResources/js/Common/sgTimepicker.directive.js b/UI/WebServerResources/js/Common/sgTimepicker.directive.js index ee6248605..91172fda8 100644 --- a/UI/WebServerResources/js/Common/sgTimepicker.directive.js +++ b/UI/WebServerResources/js/Common/sgTimepicker.directive.js @@ -10,42 +10,54 @@ template: [ '
', '
', - '
', + '
', '
', - ' {{hour.displayName}}', '
', '
', '
', '
', - '
', + '
', '
', - ' {{minute.displayName}}', '
', '
', '
', '
', - '
', - '
', - ' ', + '
', + ' {{minute.displayName}}', '
', '
', '
', - '
', + '
', ' ', '
', '
' ].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 @@ '', '
', '
', - '
', // using mdColors + '
', // using mdColors '
', - '
', + '
', ' ', '
', @@ -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; }; diff --git a/UI/WebServerResources/scss/components/timepicker/timepicker-default-theme.css b/UI/WebServerResources/scss/components/timepicker/timepicker-default-theme.css index 95030874c..df593e041 100644 --- a/UI/WebServerResources/scss/components/timepicker/timepicker-default-theme.css +++ b/UI/WebServerResources/scss/components/timepicker/timepicker-default-theme.css @@ -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; } +} \ No newline at end of file diff --git a/UI/WebServerResources/scss/components/timepicker/timepicker.scss b/UI/WebServerResources/scss/components/timepicker/timepicker.scss index b90ad17e2..8a0ce510c 100644 --- a/UI/WebServerResources/scss/components/timepicker/timepicker.scss +++ b/UI/WebServerResources/scss/components/timepicker/timepicker.scss @@ -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