muicss
Version:
Lightweight CSS framework based on Google's Material Design guidelines
326 lines (260 loc) • 11.1 kB
JavaScript
var babelHelpers = require('./babel-helpers.js');
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _angular = babelHelpers.interopRequireDefault(require("angular"));
var formlib = babelHelpers.interopRequireWildcard(require("../js/lib/forms"));
var util = babelHelpers.interopRequireWildcard(require("../js/lib/util"));
var jqLite = babelHelpers.interopRequireWildcard(require("../js/lib/jqLite"));
/**
* MUI Angular Select Component
* @module angular/select
*/
var moduleName = 'mui.select';
_angular.default.module(moduleName, []).directive('muiSelect', ['$timeout', function ($timeout) {
return {
restrict: 'AE',
require: ['ngModel'],
scope: {
label: '@',
name: '@',
placeholder: '@',
ngDisabled: '=',
ngModel: '=',
ngRequired: '='
},
replace: true,
transclude: true,
template: '<div class="mui-select" ' + 'ng-blur="onWrapperBlurOrFocus($event)" ' + 'ng-click="onWrapperClick($event)" ' + 'ng-focus="onWrapperBlurOrFocus($event)" ' + 'ng-keydown="onWrapperKeydown($event)" ' + 'ng-keypress="onWrapperKeypress($event)">' + '<select ' + 'name="{{name}}" ' + 'ng-class=\'{"mui--text-placeholder": placeholder && ngModel == ""}\' ' + 'ng-disabled="ngDisabled" ' + 'ng-model="ngModel" ' + 'ng-mousedown="onInnerMousedown($event)" ' + 'ng-required="ngRequired" ' + '>' + '<option ng-if="placeholder" value="" placeholder>{{placeholder}}</option>' + '</select>' + '<label tabindex="-1">{{label}}</label>' + '<div ' + 'class="mui-select__menu"' + 'ng-if="!useDefault && isOpen">' + '<div ' + 'ng-click="chooseOption($event, option)" ' + 'ng-repeat="option in selectEl.children() track by $index" ' + 'ng-class=\'{"mui--is-selected": $index === menuIndex, "mui--text-placeholder": option.hasAttribute("placeholder"), "mui--is-disabled": option.disabled}\' ' + 'ng-disabled="option.disabled" ' + 'ng-hide="option.hidden" ' + '>{{option.innerText}}</div>' + '</div>' + '</div>',
link: function link(scope, element, attrs, controller, transcludeFn) {
var wrapperEl = element,
selectEl = element.find('select'),
isUndef = _angular.default.isUndefined,
origValue; // disable MUI js
selectEl[0]._muiSelect = true; // init scope
scope.selectEl = selectEl;
scope.isOpen = false;
scope.useDefault = 'ontouchstart' in document.documentElement ? true : false;
scope.origTabIndex = selectEl[0].tabIndex;
scope.menuIndex = 0;
scope.q = '';
scope.qTimeout = null; // handle `use-default` attribute
if (!isUndef(attrs.useDefault)) scope.useDefault = true; // use tabIndex to make wrapper or inner focusable
if (scope.useDefault === false) {
wrapperEl.prop('tabIndex', '0');
selectEl.prop('tabIndex', '-1');
} else {
wrapperEl.prop('tabIndex', '-1');
selectEl.prop('tabIndex', '0');
} // add <option> tags to <select>
transcludeFn(function (clone) {
selectEl.append(clone);
});
function dispatchChange(option) {
selectEl[0].selectedIndex = option.index;
if (option.value !== origValue) {
scope.ngModel = option.value; // trigger change event
$timeout(function () {
util.dispatchEvent(selectEl[0], 'change', true, false);
});
}
}
/**
* Handle blur and focus events on wrapper <div> element.
* @param {Event} $event - Angular event instance
*/
scope.onWrapperBlurOrFocus = function ($event) {
// ignore events that bubbled up
if (document.activeElement !== wrapperEl[0]) return;
util.dispatchEvent(selectEl[0], $event.type, false, false);
};
/**
* Handle click event on wrapper <div> element.
* @param {Event} $event - Angular event instance
*/
scope.onWrapperClick = function ($event) {
// only left click, check default prevented and useDefault
if ($event.button !== 0 || $event.defaultPrevented || scope.useDefault || selectEl[0].disabled) {
return;
} // focus wrapper
wrapperEl[0].focus(); // open custom menu
scope.isOpen = true;
};
/**
* Handle keydown event on wrapper element.
* @param {Event} $event - Angular event instance
*/
scope.onWrapperKeydown = function ($event) {
// exit if preventDefault() was called or useDefault is true
if ($event.defaultPrevented || scope.useDefault) return;
var keyCode = $event.keyCode;
if (scope.isOpen === false) {
// spacebar, down, up
if (keyCode === 32 || keyCode === 38 || keyCode === 40) {
// prevent win scroll
$event.preventDefault(); // open menu
scope.isOpen = true;
}
} else {
// tab
if (keyCode === 9) return scope.isOpen = false; // escape | up | down | enter
if (keyCode === 27 || keyCode === 40 || keyCode === 38 || keyCode === 13) {
$event.preventDefault();
}
var options = selectEl.children(),
nextIndex = null,
i;
if (keyCode === 27) {
// escape -> close
scope.isOpen = false;
} else if (keyCode === 40) {
// down -> increment
i = scope.menuIndex + 1;
while (i < options.length) {
// exit if option not disabled
if (!options[i].disabled && !options[i].hidden) {
nextIndex = i;
break;
}
i += 1;
}
if (nextIndex !== null) scope.menuIndex = nextIndex;
} else if (keyCode === 38) {
// up -> decrement
i = scope.menuIndex - 1;
while (i > -1) {
// exit if option not disabled
if (!options[i].disabled && !options[i].hidden) {
nextIndex = i;
break;
}
i -= 1;
}
if (nextIndex !== null) scope.menuIndex = nextIndex;
} else if (keyCode === 13) {
// enter -> choose and close
dispatchChange(options[scope.menuIndex]);
scope.isOpen = false;
}
}
};
/**
* Handle keypress event on wrapper element.
* @param {Event} $event - Angular event instance
*/
scope.onWrapperKeypress = function ($event) {
// exit if preventDefault() was called or useDefault is true or
// menu is closed
if ($event.defaultPrevented || scope.useDefault || !scope.isOpen) {
return;
} // handle query timer
clearTimeout(scope.qTimeout);
scope.q += $event.key;
scope.qTimeout = setTimeout(function () {
scope.q = '';
}, 600); // select first match alphabetically
var prefixRegex = new RegExp('^' + scope.q, 'i'),
options = selectEl.children(),
m = options.length,
option,
i;
for (i = 0; i < m; i++) {
option = options[i];
if (!option.hidden && !option.disabled && prefixRegex.test(option.innerText)) {
scope.menuIndex = option.index;
break;
}
}
};
/**
* Handle mousedown event on Inner <select> element
* @param {Event} $event - Angular event instance
*/
scope.onInnerMousedown = function ($event) {
// check flag
if ($event.button !== 0 || scope.useDefault === true) return; // prevent built-in menu from opening
$event.preventDefault();
};
/**
* Choose option the user selected.
* @param {Object} option - The option selected.
*/
scope.chooseOption = function ($event, option) {
// prevent bubbling
$event.stopImmediatePropagation(); // ignore disabled
if (option.disabled) return; // dispatch change
dispatchChange(option); // close menu
scope.isOpen = false;
}; // function to close menu on window resize and document click
function closeMenuFn() {
scope.isOpen = false; // disable scroll lock
util.disableScrollLock(true); // remove event handlers
jqLite.off(document, 'click', closeMenuFn);
jqLite.off(window, 'resize', closeMenuFn);
scope.$digest();
}
/**
* Open/Close custom select menu
*/
scope.$watch('isOpen', function (isOpen, oldVal) {
// ignore first call
if (isOpen === oldVal) return; // exit if use-default is true
if (scope.useDefault === true) return;
if (isOpen === true) {
// enable scroll lock
util.enableScrollLock(); // init menuIndex
var menuEl = element.find('div'),
value = scope.ngModel,
options = selectEl.children(),
m = options.length,
i;
origValue = scope.ngModel;
scope.menuIndex = scope.menuIndex;
$timeout(function () {
// set position of custom menu
var props = formlib.getMenuPositionalCSS(element[0], menuEl[0], scope.menuIndex);
props.height = 'auto';
menuEl.css(props);
jqLite.scrollTop(menuEl[0], props.scrollTop); // attach event handlers
jqLite.on(document, 'click', closeMenuFn);
jqLite.on(window, 'resize', closeMenuFn);
});
} else {
// focus select element
selectEl[0].focus(); // disable scroll lock
util.disableScrollLock(true); // remove event handlers
jqLite.off(document, 'click', closeMenuFn);
jqLite.off(window, 'resize', closeMenuFn);
}
});
/**
* Scroll to menu items (if hidden)
*/
scope.$watch('menuIndex', function (newVal, oldVal) {
// skip initialization
if (newVal === oldVal) return; // scroll menu after rendering is finished
$timeout(function () {
var itemEl = selectEl.children()[scope.menuIndex],
itemRect = itemEl.getBoundingClientRect(),
menuEl = itemEl.parentNode;
if (itemRect.top < 0) {
// menu item is hidden above visible window
menuEl.scrollTop = menuEl.scrollTop + itemRect.top - 5;
} else if (itemRect.top > window.innerHeight) {
// menu item is hidden below visible window
menuEl.scrollTop = menuEl.scrollTop + (itemRect.top + itemRect.height - window.innerHeight) + 5;
}
});
});
scope.$watch('ngDisabled', function (newVal) {
if (newVal === true) wrapperEl.prop('tabIndex', '-1');else if (!scope.useDefault) wrapperEl.prop('tabIndex', '0');
});
}
};
}]);
/** Define module API */
var _default = moduleName;
exports.default = _default;
module.exports = exports.default;