Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/materialize-css/js/dropdown.js')
-rw-r--r--node_modules/materialize-css/js/dropdown.js617
1 files changed, 617 insertions, 0 deletions
diff --git a/node_modules/materialize-css/js/dropdown.js b/node_modules/materialize-css/js/dropdown.js
new file mode 100644
index 0000000000..899bba3a3b
--- /dev/null
+++ b/node_modules/materialize-css/js/dropdown.js
@@ -0,0 +1,617 @@
+(function($, anim) {
+ 'use strict';
+
+ let _defaults = {
+ alignment: 'left',
+ autoFocus: true,
+ constrainWidth: true,
+ container: null,
+ coverTrigger: true,
+ closeOnClick: true,
+ hover: false,
+ inDuration: 150,
+ outDuration: 250,
+ onOpenStart: null,
+ onOpenEnd: null,
+ onCloseStart: null,
+ onCloseEnd: null,
+ onItemClick: null
+ };
+
+ /**
+ * @class
+ */
+ class Dropdown extends Component {
+ constructor(el, options) {
+ super(Dropdown, el, options);
+
+ this.el.M_Dropdown = this;
+ Dropdown._dropdowns.push(this);
+
+ this.id = M.getIdFromTrigger(el);
+ this.dropdownEl = document.getElementById(this.id);
+ this.$dropdownEl = $(this.dropdownEl);
+
+ /**
+ * Options for the dropdown
+ * @member Dropdown#options
+ * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
+ * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
+ * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
+ * @prop {Element} container - Container element to attach dropdown to (optional)
+ * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
+ * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
+ * @prop {Boolean} [hover=false] - Open dropdown on hover
+ * @prop {Number} [inDuration=150] - Duration of open animation in ms
+ * @prop {Number} [outDuration=250] - Duration of close animation in ms
+ * @prop {Function} onOpenStart - Function called when dropdown starts opening
+ * @prop {Function} onOpenEnd - Function called when dropdown finishes opening
+ * @prop {Function} onCloseStart - Function called when dropdown starts closing
+ * @prop {Function} onCloseEnd - Function called when dropdown finishes closing
+ */
+ this.options = $.extend({}, Dropdown.defaults, options);
+
+ /**
+ * Describes open/close state of dropdown
+ * @type {Boolean}
+ */
+ this.isOpen = false;
+
+ /**
+ * Describes if dropdown content is scrollable
+ * @type {Boolean}
+ */
+ this.isScrollable = false;
+
+ /**
+ * Describes if touch moving on dropdown content
+ * @type {Boolean}
+ */
+ this.isTouchMoving = false;
+
+ this.focusedIndex = -1;
+ this.filterQuery = [];
+
+ // Move dropdown-content after dropdown-trigger
+ if (!!this.options.container) {
+ $(this.options.container).append(this.dropdownEl);
+ } else {
+ this.$el.after(this.dropdownEl);
+ }
+
+ this._makeDropdownFocusable();
+ this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
+ this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
+ this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
+ this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
+ this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
+ this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
+ this._setupEventHandlers();
+ }
+
+ static get defaults() {
+ return _defaults;
+ }
+
+ static init(els, options) {
+ return super.init(this, els, options);
+ }
+
+ /**
+ * Get Instance
+ */
+ static getInstance(el) {
+ let domElem = !!el.jquery ? el[0] : el;
+ return domElem.M_Dropdown;
+ }
+
+ /**
+ * Teardown component
+ */
+ destroy() {
+ this._resetDropdownStyles();
+ this._removeEventHandlers();
+ Dropdown._dropdowns.splice(Dropdown._dropdowns.indexOf(this), 1);
+ this.el.M_Dropdown = undefined;
+ }
+
+ /**
+ * Setup Event Handlers
+ */
+ _setupEventHandlers() {
+ // Trigger keydown handler
+ this.el.addEventListener('keydown', this._handleTriggerKeydownBound);
+
+ // Item click handler
+ this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);
+
+ // Hover event handlers
+ if (this.options.hover) {
+ this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
+ this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
+ this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
+ this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
+ this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);
+
+ // Click event handlers
+ } else {
+ this._handleClickBound = this._handleClick.bind(this);
+ this.el.addEventListener('click', this._handleClickBound);
+ }
+ }
+
+ /**
+ * Remove Event Handlers
+ */
+ _removeEventHandlers() {
+ this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
+ this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);
+
+ if (this.options.hover) {
+ this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
+ this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
+ this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
+ } else {
+ this.el.removeEventListener('click', this._handleClickBound);
+ }
+ }
+
+ _setupTemporaryEventHandlers() {
+ // Use capture phase event handler to prevent click
+ document.body.addEventListener('click', this._handleDocumentClickBound, true);
+ document.body.addEventListener('touchend', this._handleDocumentClickBound);
+ document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
+ this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
+ }
+
+ _removeTemporaryEventHandlers() {
+ // Use capture phase event handler to prevent click
+ document.body.removeEventListener('click', this._handleDocumentClickBound, true);
+ document.body.removeEventListener('touchend', this._handleDocumentClickBound);
+ document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
+ this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
+ }
+
+ _handleClick(e) {
+ e.preventDefault();
+ this.open();
+ }
+
+ _handleMouseEnter() {
+ this.open();
+ }
+
+ _handleMouseLeave(e) {
+ let toEl = e.toElement || e.relatedTarget;
+ let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
+ let leaveToActiveDropdownTrigger = false;
+
+ let $closestTrigger = $(toEl).closest('.dropdown-trigger');
+ if (
+ $closestTrigger.length &&
+ !!$closestTrigger[0].M_Dropdown &&
+ $closestTrigger[0].M_Dropdown.isOpen
+ ) {
+ leaveToActiveDropdownTrigger = true;
+ }
+
+ // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
+ if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
+ this.close();
+ }
+ }
+
+ _handleDocumentClick(e) {
+ let $target = $(e.target);
+ if (
+ this.options.closeOnClick &&
+ $target.closest('.dropdown-content').length &&
+ !this.isTouchMoving
+ ) {
+ // isTouchMoving to check if scrolling on mobile.
+ setTimeout(() => {
+ this.close();
+ }, 0);
+ } else if (
+ $target.closest('.dropdown-trigger').length ||
+ !$target.closest('.dropdown-content').length
+ ) {
+ setTimeout(() => {
+ this.close();
+ }, 0);
+ }
+ this.isTouchMoving = false;
+ }
+
+ _handleTriggerKeydown(e) {
+ // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdown
+ if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ENTER) && !this.isOpen) {
+ e.preventDefault();
+ this.open();
+ }
+ }
+
+ /**
+ * Handle Document Touchmove
+ * @param {Event} e
+ */
+ _handleDocumentTouchmove(e) {
+ let $target = $(e.target);
+ if ($target.closest('.dropdown-content').length) {
+ this.isTouchMoving = true;
+ }
+ }
+
+ /**
+ * Handle Dropdown Click
+ * @param {Event} e
+ */
+ _handleDropdownClick(e) {
+ // onItemClick callback
+ if (typeof this.options.onItemClick === 'function') {
+ let itemEl = $(e.target).closest('li')[0];
+ this.options.onItemClick.call(this, itemEl);
+ }
+ }
+
+ /**
+ * Handle Dropdown Keydown
+ * @param {Event} e
+ */
+ _handleDropdownKeydown(e) {
+ if (e.which === M.keys.TAB) {
+ e.preventDefault();
+ this.close();
+
+ // Navigate down dropdown list
+ } else if ((e.which === M.keys.ARROW_DOWN || e.which === M.keys.ARROW_UP) && this.isOpen) {
+ e.preventDefault();
+ let direction = e.which === M.keys.ARROW_DOWN ? 1 : -1;
+ let newFocusedIndex = this.focusedIndex;
+ let foundNewIndex = false;
+ do {
+ newFocusedIndex = newFocusedIndex + direction;
+
+ if (
+ !!this.dropdownEl.children[newFocusedIndex] &&
+ this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
+ ) {
+ foundNewIndex = true;
+ break;
+ }
+ } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
+
+ if (foundNewIndex) {
+ this.focusedIndex = newFocusedIndex;
+ this._focusFocusedItem();
+ }
+
+ // ENTER selects choice on focused item
+ } else if (e.which === M.keys.ENTER && this.isOpen) {
+ // Search for <a> and <button>
+ let focusedElement = this.dropdownEl.children[this.focusedIndex];
+ let $activatableElement = $(focusedElement)
+ .find('a, button')
+ .first();
+
+ // Click a or button tag if exists, otherwise click li tag
+ if (!!$activatableElement.length) {
+ $activatableElement[0].click();
+ } else if (!!focusedElement) {
+ focusedElement.click();
+ }
+
+ // Close dropdown on ESC
+ } else if (e.which === M.keys.ESC && this.isOpen) {
+ e.preventDefault();
+ this.close();
+ }
+
+ // CASE WHEN USER TYPE LETTERS
+ let letter = String.fromCharCode(e.which).toLowerCase(),
+ nonLetters = [9, 13, 27, 38, 40];
+ if (letter && nonLetters.indexOf(e.which) === -1) {
+ this.filterQuery.push(letter);
+
+ let string = this.filterQuery.join(''),
+ newOptionEl = $(this.dropdownEl)
+ .find('li')
+ .filter((el) => {
+ return (
+ $(el)
+ .text()
+ .toLowerCase()
+ .indexOf(string) === 0
+ );
+ })[0];
+
+ if (newOptionEl) {
+ this.focusedIndex = $(newOptionEl).index();
+ this._focusFocusedItem();
+ }
+ }
+
+ this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
+ }
+
+ /**
+ * Setup dropdown
+ */
+ _resetFilterQuery() {
+ this.filterQuery = [];
+ }
+
+ _resetDropdownStyles() {
+ this.$dropdownEl.css({
+ display: '',
+ width: '',
+ height: '',
+ left: '',
+ top: '',
+ 'transform-origin': '',
+ transform: '',
+ opacity: ''
+ });
+ }
+
+ _makeDropdownFocusable() {
+ // Needed for arrow key navigation
+ this.dropdownEl.tabIndex = 0;
+
+ // Only set tabindex if it hasn't been set by user
+ $(this.dropdownEl)
+ .children()
+ .each(function(el) {
+ if (!el.getAttribute('tabindex')) {
+ el.setAttribute('tabindex', 0);
+ }
+ });
+ }
+
+ _focusFocusedItem() {
+ if (
+ this.focusedIndex >= 0 &&
+ this.focusedIndex < this.dropdownEl.children.length &&
+ this.options.autoFocus
+ ) {
+ this.dropdownEl.children[this.focusedIndex].focus();
+ }
+ }
+
+ _getDropdownPosition() {
+ let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
+ let triggerBRect = this.el.getBoundingClientRect();
+ let dropdownBRect = this.dropdownEl.getBoundingClientRect();
+
+ let idealHeight = dropdownBRect.height;
+ let idealWidth = dropdownBRect.width;
+ let idealXPos = triggerBRect.left - dropdownBRect.left;
+ let idealYPos = triggerBRect.top - dropdownBRect.top;
+
+ let dropdownBounds = {
+ left: idealXPos,
+ top: idealYPos,
+ height: idealHeight,
+ width: idealWidth
+ };
+
+ // Countainer here will be closest ancestor with overflow: hidden
+ let closestOverflowParent = !!this.dropdownEl.offsetParent
+ ? this.dropdownEl.offsetParent
+ : this.dropdownEl.parentNode;
+
+ let alignments = M.checkPossibleAlignments(
+ this.el,
+ closestOverflowParent,
+ dropdownBounds,
+ this.options.coverTrigger ? 0 : triggerBRect.height
+ );
+
+ let verticalAlignment = 'top';
+ let horizontalAlignment = this.options.alignment;
+ idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
+
+ // Reset isScrollable
+ this.isScrollable = false;
+
+ if (!alignments.top) {
+ if (alignments.bottom) {
+ verticalAlignment = 'bottom';
+ } else {
+ this.isScrollable = true;
+
+ // Determine which side has most space and cutoff at correct height
+ if (alignments.spaceOnTop > alignments.spaceOnBottom) {
+ verticalAlignment = 'bottom';
+ idealHeight += alignments.spaceOnTop;
+ idealYPos -= alignments.spaceOnTop;
+ } else {
+ idealHeight += alignments.spaceOnBottom;
+ }
+ }
+ }
+
+ // If preferred horizontal alignment is possible
+ if (!alignments[horizontalAlignment]) {
+ let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
+ if (alignments[oppositeAlignment]) {
+ horizontalAlignment = oppositeAlignment;
+ } else {
+ // Determine which side has most space and cutoff at correct height
+ if (alignments.spaceOnLeft > alignments.spaceOnRight) {
+ horizontalAlignment = 'right';
+ idealWidth += alignments.spaceOnLeft;
+ idealXPos -= alignments.spaceOnLeft;
+ } else {
+ horizontalAlignment = 'left';
+ idealWidth += alignments.spaceOnRight;
+ }
+ }
+ }
+
+ if (verticalAlignment === 'bottom') {
+ idealYPos =
+ idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
+ }
+ if (horizontalAlignment === 'right') {
+ idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
+ }
+ return {
+ x: idealXPos,
+ y: idealYPos,
+ verticalAlignment: verticalAlignment,
+ horizontalAlignment: horizontalAlignment,
+ height: idealHeight,
+ width: idealWidth
+ };
+ }
+
+ /**
+ * Animate in dropdown
+ */
+ _animateIn() {
+ anim.remove(this.dropdownEl);
+ anim({
+ targets: this.dropdownEl,
+ opacity: {
+ value: [0, 1],
+ easing: 'easeOutQuad'
+ },
+ scaleX: [0.3, 1],
+ scaleY: [0.3, 1],
+ duration: this.options.inDuration,
+ easing: 'easeOutQuint',
+ complete: (anim) => {
+ if (this.options.autoFocus) {
+ this.dropdownEl.focus();
+ }
+
+ // onOpenEnd callback
+ if (typeof this.options.onOpenEnd === 'function') {
+ this.options.onOpenEnd.call(this, this.el);
+ }
+ }
+ });
+ }
+
+ /**
+ * Animate out dropdown
+ */
+ _animateOut() {
+ anim.remove(this.dropdownEl);
+ anim({
+ targets: this.dropdownEl,
+ opacity: {
+ value: 0,
+ easing: 'easeOutQuint'
+ },
+ scaleX: 0.3,
+ scaleY: 0.3,
+ duration: this.options.outDuration,
+ easing: 'easeOutQuint',
+ complete: (anim) => {
+ this._resetDropdownStyles();
+
+ // onCloseEnd callback
+ if (typeof this.options.onCloseEnd === 'function') {
+ this.options.onCloseEnd.call(this, this.el);
+ }
+ }
+ });
+ }
+
+ /**
+ * Place dropdown
+ */
+ _placeDropdown() {
+ // Set width before calculating positionInfo
+ let idealWidth = this.options.constrainWidth
+ ? this.el.getBoundingClientRect().width
+ : this.dropdownEl.getBoundingClientRect().width;
+ this.dropdownEl.style.width = idealWidth + 'px';
+
+ let positionInfo = this._getDropdownPosition();
+ this.dropdownEl.style.left = positionInfo.x + 'px';
+ this.dropdownEl.style.top = positionInfo.y + 'px';
+ this.dropdownEl.style.height = positionInfo.height + 'px';
+ this.dropdownEl.style.width = positionInfo.width + 'px';
+ this.dropdownEl.style.transformOrigin = `${
+ positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
+ } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
+ }
+
+ /**
+ * Open Dropdown
+ */
+ open() {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+
+ // onOpenStart callback
+ if (typeof this.options.onOpenStart === 'function') {
+ this.options.onOpenStart.call(this, this.el);
+ }
+
+ // Reset styles
+ this._resetDropdownStyles();
+ this.dropdownEl.style.display = 'block';
+
+ this._placeDropdown();
+ this._animateIn();
+ this._setupTemporaryEventHandlers();
+ }
+
+ /**
+ * Close Dropdown
+ */
+ close() {
+ if (!this.isOpen) {
+ return;
+ }
+ this.isOpen = false;
+ this.focusedIndex = -1;
+
+ // onCloseStart callback
+ if (typeof this.options.onCloseStart === 'function') {
+ this.options.onCloseStart.call(this, this.el);
+ }
+
+ this._animateOut();
+ this._removeTemporaryEventHandlers();
+
+ if (this.options.autoFocus) {
+ this.el.focus();
+ }
+ }
+
+ /**
+ * Recalculate dimensions
+ */
+ recalculateDimensions() {
+ if (this.isOpen) {
+ this.$dropdownEl.css({
+ width: '',
+ height: '',
+ left: '',
+ top: '',
+ 'transform-origin': ''
+ });
+ this._placeDropdown();
+ }
+ }
+ }
+
+ /**
+ * @static
+ * @memberof Dropdown
+ */
+ Dropdown._dropdowns = [];
+
+ M.Dropdown = Dropdown;
+
+ if (M.jQueryLoaded) {
+ M.initializeJqueryWrapper(Dropdown, 'dropdown', 'M_Dropdown');
+ }
+})(cash, M.anime);