kahuna/public/js/components/gr-chips/gr-chip-input.js (132 lines of code) (raw):
import angular from 'angular';
export const grChipInput = angular.module('gr.chipInput', []);
const ENTER_KEY = 13;
const BACKSPACE_KEY = 8;
const DELETE_KEY = 46;
const LEFT_KEY = 37;
const RIGHT_KEY = 39;
const HOME_KEY = 36;
const END_KEY = 35;
function keyHandler(handler) {
return function (event) {
const {which, target} = event;
const caretStart = target.selectionStart;
const caretEnd = target.selectionEnd;
const input = target.value;
const inputLen = input.length;
const atInputStart = caretStart === 0 && caretEnd === 0;
const atInputEnd = caretStart === inputLen && caretEnd === inputLen;
const noCharBefore = caretStart === 0 || input[caretStart - 1] === ' ';
const handled = handler({
which,
atInputStart,
atInputEnd,
noCharBefore,
caretStart,
caretEnd
});
if (handled) {
event.preventDefault();
event.stopPropagation();
}
};
}
grChipInput.directive('grChipInput', ['$parse', '$timeout', function($parse, $timeout) {
return {
restrict: 'A',
require: ['^grChip', '^grChips'],
link: function(scope, element, attrs, [$grChipCtrl, $grChipsCtrl]) {
const [
onEnterExpr,
backspaceStartExpr,
deleteEndExpr
] = [
attrs.grChipInputOnEnter,
attrs.grChipInputBackspaceAtStart,
attrs.grChipInputDeleteAtEnd
].map(attr => attr && $parse(attr));
scope.$watchCollection(() => ({focusedItem: $grChipsCtrl.focusedItem,
caretStartOffset: $grChipsCtrl.caretStartOffset,
caretEndOffset: $grChipsCtrl.caretEndOffset}),
({focusedItem, caretStartOffset, caretEndOffset}) => {
// Focus self (unless already focused)
if (focusedItem === $grChipCtrl.chip) {
const selStart = element[0].selectionStart;
const selEnd = element[0].selectionEnd;
// Unless already focused at the same caret location
if (document.activeElement !== element[0] ||
selStart !== caretStartOffset ||
selEnd !== caretEndOffset) {
// Yield to avoid digest-within-digest of focus event
$timeout(() => {
element[0].focus();
element[0].setSelectionRange(caretStartOffset, caretEndOffset);
});
}
}
});
element.on('focus', () => {
// Caret position not set yet, yield to get it
$timeout(() => {
const selStart = element[0].selectionStart;
const selEnd = element[0].selectionEnd;
$grChipsCtrl.setFocusedChip($grChipCtrl.chip, selStart, selEnd);
});
});
element.on('blur', () => {
$grChipsCtrl.unsetFocusedChip($grChipCtrl.chip);
scope.$digest();
});
element.on('input', () => {
const selStart = element[0].selectionStart;
const selEnd = element[0].selectionEnd;
$grChipsCtrl.onChange();
$grChipsCtrl.setFocusedChip($grChipCtrl.chip, selStart, selEnd);
scope.$apply();
});
element.on('keydown', keyHandler(function({which, atInputStart, atInputEnd}) {
switch (which) {
case ENTER_KEY:
if (onEnterExpr) {
onEnterExpr(scope);
scope.$apply();
return true;
}
break;
case LEFT_KEY:
if (atInputStart) {
$grChipsCtrl.focusEndOfChipBefore($grChipCtrl.chip);
scope.$apply();
return true;
}
break;
case RIGHT_KEY:
if (atInputEnd) {
$grChipsCtrl.focusStartOfChipAfter($grChipCtrl.chip);
scope.$apply();
return true;
}
break;
case BACKSPACE_KEY:
if (atInputStart && backspaceStartExpr) {
backspaceStartExpr(scope);
scope.$apply();
return true;
}
break;
case DELETE_KEY:
if (atInputEnd && deleteEndExpr) {
deleteEndExpr(scope);
scope.$apply();
return true;
}
break;
// Emulate Home/End to go to start/end of input
case HOME_KEY:
$grChipsCtrl.focusStartOfFirstChip();
scope.$apply();
return true;
case END_KEY:
$grChipsCtrl.focusEndOfLastChip();
scope.$apply();
return true;
}
}));
}
};
}]);