Skip to content

Commit

Permalink
Append picker to body
Browse files Browse the repository at this point in the history
 - Transcluded content are no more wrapped into 2 `<div>`s
 - Updated style (fixed picker z-index to `1000`)
 - Improved performances during scroll (`position` fn)
 - Set value updates `model` and `$modelValue`
 - Added `touchstart` event in addition of `click`
  • Loading branch information
indrimuska committed May 13, 2017
1 parent e6619c0 commit 3cfb8cb
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 33 deletions.
1 change: 0 additions & 1 deletion src/definitions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export interface IDirectiveScopeInternal extends IDirectiveScope, IProviderOptio

// elements
picker: ng.IAugmentedJQuery;
contents: ng.IAugmentedJQuery;
container: ng.IAugmentedJQuery;
input: ng.IAugmentedJQuery;
}
Expand Down
55 changes: 35 additions & 20 deletions src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,28 +158,42 @@ export default class Directive implements ng.IDirective {

$scope.isOpen = true;
$scope.view.isOpen = true;
this.$timeout($scope.view.position, 0, false);
document.body.appendChild($scope.picker[0]);
$scope.view.position();
},
close: () => {
if (!$scope.view.isOpen || $scope.inline) return;

$scope.isOpen = false;
$scope.view.isOpen = false;
$scope.view.selected = $scope.startView;
$scope.picker[0].parentNode.removeChild($scope.picker[0]);
},
position: () => {
if (!$scope.view.isOpen || $scope.position || $scope.inline) return;
$scope.picker.removeClass('top').removeClass('right');

let container = $scope.container[0],
offset = getOffset(container),
let element = $element[0],
picker = $scope.picker[0],
hasClassTop = $scope.picker.hasClass('top'),
hasClassRight = $scope.picker.hasClass('right'),
offset = getOffset($element[0]),
top = offset.top - this.$window.pageYOffset,
left = offset.left - this.$window.pageXOffset,
winWidth = this.$window.innerWidth,
winHeight = this.$window.innerHeight;
winHeight = this.$window.innerHeight,
shouldHaveClassTop = top + this.$window.pageYOffset - picker.offsetHeight > 0 && top > winHeight / 2,
shouldHaveClassRight = left + picker.offsetWidth > winWidth,
pickerTop = offset.top + (shouldHaveClassTop ? 0 : element.offsetHeight) + 'px',
pickerLeft = offset.left + 'px',
pickerWidth = element.offsetWidth + 'px';

if (top + this.$window.pageYOffset - container.offsetHeight > 0 && top > winHeight / 2) $scope.picker.addClass('top');
if (left + container.offsetWidth > winWidth) $scope.picker.addClass('right');
if (!hasClassTop && shouldHaveClassTop) $scope.picker.addClass('top');
if (hasClassTop && !shouldHaveClassTop) $scope.picker.removeClass('top');
if (!hasClassRight && shouldHaveClassRight) $scope.picker.addClass('right');
if (hasClassRight && !shouldHaveClassRight) $scope.picker.removeClass('right');
if ($scope.picker.css('top') !== pickerTop) $scope.picker.css('top', pickerTop);
if ($scope.picker.css('left') !== pickerLeft) $scope.picker.css('left', pickerLeft);
if ($scope.picker.css('width') !== pickerWidth) $scope.picker.css('width', pickerWidth);
},
keydown: (e) => {
let view: IView = $scope.views[$scope.view.selected],
Expand Down Expand Up @@ -278,16 +292,16 @@ export default class Directive implements ng.IDirective {
};

// creation
$element.addClass('moment-picker-reference').prepend($transElement);
$scope.picker = angular.element($element[0].querySelectorAll('.moment-picker'));
$element.after($scope.picker);
$scope.contents = angular.element($scope.picker[0].querySelectorAll('.moment-picker-contents'));
$scope.container = angular.element($scope.picker[0].querySelectorAll('.moment-picker-container'));
$scope.contents.append($element.append($transElement));
$scope.input = $scope.contents[0].tagName.toLowerCase() != 'input' && $scope.contents[0].querySelectorAll('input').length > 0
? angular.element($scope.contents[0].querySelectorAll('input'))
: angular.element($scope.contents[0]);
$scope.input.addClass('moment-picker-input').attr('tabindex', 0);
$scope.input = $element[0].tagName.toLowerCase() != 'input' && $element[0].querySelectorAll('input').length > 0
? angular.element($element[0].querySelectorAll('input'))
: angular.element($element[0]);
$scope.input.attr('tabindex', 0);
($scope.position || '').split(' ').forEach((className: string) => $scope.picker.addClass(className));
if (!$scope.inline) $scope.picker[0].parentNode.removeChild($scope.picker[0]);
else $scope.picker.addClass('inline');

// transclude scope to template additions
this.$timeout(() => {
Expand Down Expand Up @@ -406,20 +420,21 @@ export default class Directive implements ng.IDirective {
if (e) e.preventDefault();
$scope.input[0].focus();
};
// use `touchstart` for iOS Safari, where click events aren't propogated under most circumstances.
$scope.input
.on('focus click', () => $scope.$evalAsync($scope.view.open))
.on('blur', () => $scope.$evalAsync($scope.view.close))
.on('keydown', (e) => {
.on('focus click touchstart', () => $scope.$evalAsync($scope.view.open))
.on('blur', () => $scope.$evalAsync($scope.view.close))
.on('keydown', (e) => {
if ($scope.keyboard) $scope.$evalAsync(() => $scope.view.keydown(e));
});
$scope.contents.on('click', () => focusInput());
$element.on('click touchstart', () => focusInput());
$scope.container.on('mousedown', (e: JQueryEventObject) => focusInput(e));
angular.element(this.$window).on('resize scroll', $scope.view.position);

// unbind events on destroy
$scope.$on('$destroy', () => {
$scope.input.off('focus click blur keydown');
$scope.contents.off('click');
$scope.input.off('focus click touchstart blur keydown');
$element.off('click touchstart');
$scope.container.off('mousedown');
angular.element(this.$window).off('resize scroll', $scope.view.position);
});
Expand Down
17 changes: 10 additions & 7 deletions src/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
@selected-bg-image: linear-gradient(#45b1e8, #3097de);
@selected-border-color: #3ca0dd;

.moment-picker-reference { cursor: pointer; }
.moment-picker {
position: relative;
display: block;
position: absolute;
z-index: 1000;

.moment-picker-input { cursor: pointer; }
.moment-picker-container {
color: @text-color;
min-width: 15em;
Expand All @@ -33,15 +33,19 @@
border: 1px solid @border-color;
border-radius: 4px;
position: absolute;
top: 100%;
margin-top: 4px;
margin-left: -.5em;
box-shadow: 0 2px 4px @box-shadow-color;
z-index: 5;
&:before, &:after { content: ''; display: block; width: 0; height: 0; border: 8px solid transparent; border-top: none; position: absolute; top: -9px; left: 15px; }
&:before { border-bottom-color: @border-color; border-width: 9px; }
&:after { border-bottom-color: @background; margin-top: 1px; margin-left: 1px; }
&.inline {
}

// inline picker
&.inline {
display: block;
position: relative;
.moment-picker-container {
position: relative;
margin: 0;
&:before, &:after { content: none; }
Expand All @@ -50,7 +54,6 @@

// picker on top
&.top .moment-picker-container {
top: auto;
bottom: 100%;
margin-top: auto;
margin-bottom: 4px;
Expand Down
5 changes: 2 additions & 3 deletions src/template.tpl.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<div class="moment-picker">
<span class="moment-picker-contents"></span>
<div class="moment-picker-container {{view.selected}}-view" ng-show="(view.isOpen && !disabled) || inline"
ng-class="{'moment-picker-disabled': disabled, open: view.isOpen, inline: inline}">
<div class="moment-picker-container {{view.selected}}-view"
ng-class="{'moment-picker-disabled': disabled, open: view.isOpen}">
<div ng-if="additions.top" class="moment-picker-addition top"></div>
<table class="header-view" ng-if="showHeader">
<thead>
Expand Down
4 changes: 2 additions & 2 deletions src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export const valueToMoment = (formattedValue: Value, $scope: IDirectiveScopeInte
export const setValue = (value: moment.Moment | Value, $scope: IDirectiveScopeInternal, $ctrl: IModelController, $attrs: ng.IAttributes): void => {
let modelValue = isValidMoment(value) ? (<moment.Moment>value).clone() : valueToMoment(<Value>value, $scope),
viewValue = momentToValue(modelValue, $scope.format);
updateMoment($scope.model, modelValue, $scope);
updateMoment($ctrl.$modelValue, modelValue, $scope);
$scope.model = updateMoment($scope.model, modelValue, $scope);
$ctrl.$modelValue = updateMoment($ctrl.$modelValue, modelValue, $scope);
if ($attrs['ngModel'] != $attrs['momentPicker']) $scope.value = viewValue;
if ($attrs['ngModel']) {
$ctrl.$setViewValue(viewValue);
Expand Down

0 comments on commit 3cfb8cb

Please sign in to comment.