menu_bootstrap_icon-1.x-dev/js/bootstrapicon-iconpicker.js

js/bootstrapicon-iconpicker.js
/*!
 * Bootstrap Icons
 * https://github.com/pixel-s-lab/bootstrapicon-iconpicker
 * This script is based on fontawesome-iconpicker by Javi Aguilar
 * @author Madalin Marius Stan, pixel.com.ro
 * @license MIT License
 * @see https://github.com/pixel-s-lab/bootstrapicon-iconpicker/blob/main/LICENSE
 */

(function (factory) {
  if (typeof define === "function" && define.amd) {
    // AMD. Register as an anonymous module.
    define(["jquery"], factory);
  } else {
    // Browser globals
    factory(jQuery);
  }
})(function ($) {
  $.ui = $.ui || {};
  let version = $.ui.version = "1.12.1";

  (function () {
    let cachedScrollbarWidth,
      max = Math.max,
      abs = Math.abs,
      rhorizontal = /left|center|right/,
      rvertical = /top|center|bottom/,
      roffset = /[\+\-]\d+(\.[\d]+)?%?/,
      rposition = /^\w+/,
      rpercent = /%$/,
      _position = $.fn.pos;

    function getOffsets(offsets, width, height) {
      return [
        parseFloat(offsets[0]) * (rpercent.test(offsets[0]) ? width / 100 : 1),
        parseFloat(offsets[1]) * (rpercent.test(offsets[1]) ? height / 100 : 1)
      ];
    }

    function parseCss(element, property) {
      return parseInt($.css(element, property), 10) || 0;
    }

    function getDimensions(elem) {
      const raw = elem[0];
      if (raw.nodeType === 9) {
        return {
          width: elem.width(),
          height: elem.height(),
          offset: {
            top: 0,
            left: 0
          }
        };
      }
      if (raw != null && raw === raw.window) {
        return {
          width: elem.width(),
          height: elem.height(),
          offset: {
            top: elem.scrollTop(),
            left: elem.scrollLeft()
          }
        };
      }
      if (raw.preventDefault) {
        return {
          width: 0,
          height: 0,
          offset: {
            top: raw.pageY,
            left: raw.pageX
          }
        };
      }
      return {
        width: elem.outerWidth(),
        height: elem.outerHeight(),
        offset: elem.offset()
      };
    }

    $.pos = {
      scrollbarWidth: (() => {
        let cachedScrollbarWidth;
        return function () {
          if (cachedScrollbarWidth !== undefined) {
            return cachedScrollbarWidth;
          }
          const div = document.createElement("div");
          div.style.cssText = "display:block;position:absolute;width:50px;height:50px;overflow:hidden;";
          const innerDiv = document.createElement("div");
          innerDiv.style.cssText = "height:100px;width:auto;";
          div.appendChild(innerDiv);
          document.body.appendChild(div);
          const w1 = innerDiv.offsetWidth;
          div.style.overflow = "scroll";
          const w2 = innerDiv.offsetWidth || div.clientWidth;
          document.body.removeChild(div);
          cachedScrollbarWidth = w1 - w2;
          return cachedScrollbarWidth;
        };
      })(),
      getScrollInfo: function (within) {
        const overflowX = within.isWindow || within.isDocument ? "" : getComputedStyle(within.element).overflowX;
        const overflowY = within.isWindow || within.isDocument ? "" : getComputedStyle(within.element).overflowY;

        const hasOverflowX = overflowX === "scroll" || (overflowX === "auto" && within.width < within.element.scrollWidth);
        const hasOverflowY = overflowY === "scroll" || (overflowY === "auto" && within.height < within.element.scrollHeight);

        return {
          width: hasOverflowY ? pos.scrollbarWidth() : 0,
          height: hasOverflowX ? pos.scrollbarWidth() : 0,
        };
      },
      getWithinInfo:  function (element = window) {
        const isWindow = element === window;
        const isDocument = element.nodeType === 9; // DOCUMENT_NODE

        const withinElement = isWindow ? window : isDocument ? document.documentElement : element;

        return {
          element: withinElement,
          isWindow,
          isDocument,
          offset: isWindow || isDocument ? { left: 0, top: 0 } : withinElement.getBoundingClientRect(),
          scrollLeft: withinElement.scrollLeft || 0,
          scrollTop: withinElement.scrollTop || 0,
          width: isWindow ? window.innerWidth : withinElement.clientWidth,
          height: isWindow ? window.innerHeight : withinElement.clientHeight,
        };
      },
    };
    $.fn.pos = function (options) {
      if (!options || !options.of) {
        return _position.apply(this, arguments);
      }

      // Clone options to prevent modification
      const settings = { ...options };

      const target = $(settings.of);
      const within = $.pos.getWithinInfo(settings.within);
      const scrollInfo = $.pos.getScrollInfo(within);
      const collision = (settings.collision || "flip").split(" ");
      const offsets = {};

      const dimensions = getDimensions(target);
      const targetWidth = dimensions.width;
      const targetHeight = dimensions.height;
      const targetOffset = dimensions.offset;

      // Adjust base position based on "at" alignment
      let basePosition = { ...targetOffset };

      const normalizePositions = (positionArray, axis) => {
        if (positionArray.length === 1) {
          return axis === "horizontal"
            ? rhorizontal.test(positionArray[0])
              ? [positionArray[0], "center"]
              : ["center", "center"]
            : ["center", positionArray[0]];
        }
        return positionArray;
      };

      ["my", "at"].forEach((key) => {
        const position = normalizePositions((settings[key] || "").split(" "), key === "my" ? "horizontal" : "vertical");
        const [horizontalOffset, verticalOffset] = [roffset.exec(position[0]), roffset.exec(position[1])];
        offsets[key] = [
          horizontalOffset ? parseFloat(horizontalOffset[0]) : 0,
          verticalOffset ? parseFloat(verticalOffset[0]) : 0,
        ];
        settings[key] = [
          rposition.exec(position[0])[0] || "center",
          rposition.exec(position[1])[0] || "center",
        ];
      });

      // Adjust position based on "at" alignment
      if (settings.at[0] === "right") basePosition.left += targetWidth;
      else if (settings.at[0] === "center") basePosition.left += targetWidth / 2;

      if (settings.at[1] === "bottom") basePosition.top += targetHeight;
      else if (settings.at[1] === "center") basePosition.top += targetHeight / 2;

      const atOffset = getOffsets(offsets.at, targetWidth, targetHeight);
      basePosition.left += atOffset[0];
      basePosition.top += atOffset[1];

      // Apply the calculated position to each element
      return this.each(function () {
        const elem = $(this);
        const elemWidth = elem.outerWidth();
        const elemHeight = elem.outerHeight();
        const marginLeft = parseCss(this, "marginLeft");
        const marginTop = parseCss(this, "marginTop");

        const collisionWidth = elemWidth + marginLeft + parseCss(this, "marginRight") + scrollInfo.width;
        const collisionHeight = elemHeight + marginTop + parseCss(this, "marginBottom") + scrollInfo.height;

        const myOffset = getOffsets(offsets.my, elemWidth, elemHeight);

        // Adjust position based on "my" alignment
        const position = {
          ...basePosition,
          left: basePosition.left + myOffset[0] - (settings.my[0] === "right" ? elemWidth : settings.my[0] === "center" ? elemWidth / 2 : 0),
          top: basePosition.top + myOffset[1] - (settings.my[1] === "bottom" ? elemHeight : settings.my[1] === "center" ? elemHeight / 2 : 0),
        };

        // Handle collision logic
        const collisionPosition = { marginLeft, marginTop };
        ["left", "top"].forEach((dir, i) => {
          if ($.ui.pos[collision[i]]) {
            $.ui.pos[collision[i]][dir](position, {
              targetWidth,
              targetHeight,
              elemWidth,
              elemHeight,
              collisionPosition,
              collisionWidth,
              collisionHeight,
              offset: [atOffset[0] + myOffset[0], atOffset[1] + myOffset[1]],
              my: settings.my,
              at: settings.at,
              within,
              elem,
            });
          }
        });

        // If a custom "using" function is provided, call it
        if (settings.using) {
          settings.using.call(this, position, {
            target: {
              element: target,
              left: targetOffset.left,
              top: targetOffset.top,
              width: targetWidth,
              height: targetHeight,
            },
            element: {
              element: elem,
              left: position.left,
              top: position.top,
              width: elemWidth,
              height: elemHeight,
            },
          });
        }

        elem.offset(position);
      });
    };
    $.ui.pos = {
      _trigger(position, data, name, triggered) {
        if (data.elem) {
          data.elem.trigger({
            type: name,
            position,
            positionData: data,
            triggered,
          });
        }
      },

      _handleOverflow(position, data, dir, axis) {
        const { within, collisionPosition, collisionWidth, collisionHeight } = data;
        const withinOffset = axis === "x" ? within.scrollLeft : within.scrollTop;
        const outerSize = axis === "x" ? within.width : within.height;
        const collisionPos = axis === "x" ? position.left : position.top;
        const elemSize = axis === "x" ? collisionWidth : collisionHeight;

        const overStart = withinOffset - (collisionPos - collisionPosition[`margin${dir}`]);
        const overEnd = collisionPos + elemSize - outerSize - withinOffset;

        let newOverEnd;
        if (elemSize > outerSize) {
          if (overStart > 0 && overEnd <= 0) {
            newOverEnd = collisionPos + overStart + elemSize - outerSize - withinOffset;
            return collisionPos + overStart - newOverEnd;
          }
          if (overEnd > 0 && overStart <= 0) return withinOffset;
          return overStart > overEnd
            ? withinOffset + outerSize - elemSize
            : withinOffset;
        }

        if (overStart > 0) return collisionPos + overStart;
        if (overEnd > 0) return collisionPos - overEnd;
        return Math.max(collisionPos - (collisionPos - collisionPosition[`margin${dir}`]), collisionPos);
      },

      fit: {
        left(position, data) {
          $.ui.pos._trigger(position, data, "posCollide", "fitLeft");
          position.left = $.ui.pos._handleOverflow(position, data, "Left", "x");
          $.ui.pos._trigger(position, data, "posCollided", "fitLeft");
        },
        top(position, data) {
          $.ui.pos._trigger(position, data, "posCollide", "fitTop");
          position.top = $.ui.pos._handleOverflow(position, data, "Top", "y");
          $.ui.pos._trigger(position, data, "posCollided", "fitTop");
        },
      },

      flip: {
        left(position, data) {
          $.ui.pos._trigger(position, data, "posCollide", "flipLeft");
          const { within, my, at, offset, targetWidth, elemWidth } = data;
          const withinOffset = within.offset.left + within.scrollLeft;
          const outerWidth = within.width;
          const collisionPosLeft = position.left - data.collisionPosition.marginLeft;
          const overLeft = collisionPosLeft - withinOffset;
          const overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset;

          const myOffset = my[0] === "left" ? -elemWidth : my[0] === "right" ? elemWidth : 0;
          const atOffset = at[0] === "left" ? targetWidth : at[0] === "right" ? -targetWidth : 0;
          const totalOffset = myOffset + atOffset + offset[0] * -2;

          if (overLeft < 0 && (overRight <= 0 || Math.abs(overLeft) < overRight)) {
            position.left += totalOffset;
          } else if (overRight > 0 && (overLeft >= 0 || Math.abs(overRight) < overLeft)) {
            position.left += totalOffset;
          }
          $.ui.pos._trigger(position, data, "posCollided", "flipLeft");
        },
        top(position, data) {
          $.ui.pos._trigger(position, data, "posCollide", "flipTop");
          const { within, my, at, offset, targetHeight, elemHeight } = data;
          const withinOffset = within.offset.top + within.scrollTop;
          const outerHeight = within.height;
          const collisionPosTop = position.top - data.collisionPosition.marginTop;
          const overTop = collisionPosTop - withinOffset;
          const overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset;

          const myOffset = my[1] === "top" ? -elemHeight : my[1] === "bottom" ? elemHeight : 0;
          const atOffset = at[1] === "top" ? targetHeight : at[1] === "bottom" ? -targetHeight : 0;
          const totalOffset = myOffset + atOffset + offset[1] * -2;

          if (overTop < 0 && (overBottom <= 0 || Math.abs(overTop) < overBottom)) {
            position.top += totalOffset;
          } else if (overBottom > 0 && (overTop >= 0 || Math.abs(overBottom) < overTop)) {
            position.top += totalOffset;
          }
          $.ui.pos._trigger(position, data, "posCollided", "flipTop");
        },
      },

      flipfit: {
        left(...args) {
          $.ui.pos.flip.left(...args);
          $.ui.pos.fit.left(...args);
        },
        top(...args) {
          $.ui.pos.flip.top(...args);
          $.ui.pos.fit.top(...args);
        },
      },
    };
    (function () {
      const body = document.body;
      const fakeBody = document.createElement("div");
      const testDiv = document.createElement("div");

      // Apply styles to the fake body for testing
      Object.assign(fakeBody.style, {
        visibility: "hidden",
        width: "0",
        height: "0",
        border: "none",
        margin: "0",
        background: "none",
        position: "absolute",
        left: "-1000px",
        top: "-1000px",
      });

      // Add test div for measuring offset
      testDiv.style.cssText = "position: absolute; left: 10.7432222px;";
      fakeBody.appendChild(testDiv);

      // Insert the fake body into the DOM
      (body || document.documentElement).appendChild(fakeBody);

      // Determine if the browser supports fractional offsets
      const offsetLeft = testDiv.getBoundingClientRect().left;
      $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;

      // Clean up by removing the fake body
      fakeBody.remove();
    })();
  })();
  var position = $.ui.position;
});

(function (factory) {
  "use strict";
  if (typeof define === 'function' && define.amd) {
    define(['jquery'], factory);
  } else if (window.jQuery && !window.jQuery.fn.iconpicker) {
    factory(window.jQuery);
  }
})(function ($) {
  "use strict";
  const _helpers = {
    isEmpty: (val) => val === false || val === '' || val == null,
    isEmptyObject: (val) => _helpers.isEmpty(val) || (val.length === 0 && typeof val !== "undefined"),
    isElement: (selector) => document.querySelector(selector) !== null,
    isString: (val) => typeof val === "string" || val instanceof String,
    isArray: Array.isArray,
    inArray: (val, arr) => arr.includes(val),
    throwError: (text) => {
      throw new Error(`Font Awesome Icon Picker Exception: ${text}`);
    },
  };

  const Iconpicker = function (element, options) {
    this._id = Iconpicker._idCounter++;
    this.element = $(element).addClass('iconpicker-element');
    this._trigger('iconpickerCreate', {
      iconpickerValue: this.iconpickerValue
    });
    this.options = $.extend({}, Iconpicker.defaultOptions, this.element.data(), options);
    this.options.templates = $.extend({}, Iconpicker.defaultOptions.templates, this.options.templates);
    this.options.originalPlacement = this.options.placement;
    // Iconpicker container element
    this.container = (_helpers.isElement(this.options.container) ? $(this.options.container) : false);
    if (this.container === false) {
      if (this.element.is('.dropdown-toggle')) {
        this.container = $('~ .dropdown-menu:first', this.element);
      } else {
        this.container = (this.element.is('input,textarea,button,.btn') ? this.element.parent() : this.element);
      }
    }
    this.container.addClass('iconpicker-container');

    if (this.isDropdownMenu()) {
      this.options.placement = 'inline';
    }

    // Is the element an input? Should we search inside for any input?
    this.input = (this.element.is('input,textarea') ? this.element.addClass('iconpicker-input') : false);
    if (this.input === false) {
      this.input = (this.container.find(this.options.input));
      if (!this.input.is('input,textarea')) {
        this.input = false;
      }
    }

    // Plugin as component ?
    this.component = this.isDropdownMenu() ? this.container.parent().find(this.options.component) : this.container.find(this.options.component);
    if (this.component.length === 0) {
      this.component = false;
    } else {
      this.component.find('i').addClass('iconpicker-component');
    }

    // Create popover and iconpicker HTML
    this._createPopover();
    this._createIconpicker();

    if (this.getAcceptButton().length === 0) {
      // disable this because we don't have accept buttons
      this.options.mustAccept = false;
    }

    // Avoid CSS issues with input-group-addon(s)
    if (this.isInputGroup()) {
      this.container.parent().append(this.popover);
    } else {
      this.container.append(this.popover);
    }

    // Bind events
    this._bindElementEvents();
    this._bindWindowEvents();

    // Refresh everything
    this.update(this.options.selected);

    if (this.isInline()) {
      this.show();
    }

    this._trigger('iconpickerCreated', {
      iconpickerValue: this.iconpickerValue
    });
  };

  // Instance identifier counter
  Iconpicker._idCounter = 0;
  Iconpicker.defaultOptions = {
    title: false, // Popover title (optional) only if specified in the template
    selected: false, // use this value as the current item and ignore the original
    defaultValue: false, // use this value as the current item if input or element value is empty
    placement: 'bottom', // (has some issues with auto and CSS). auto, top, bottom, left, right
    collision: 'none', // If true, the popover will be repositioned to another position when collapses with the window borders
    animation: true, // fade in/out on show/hide ?
    //hide iconpicker automatically when a value is picked. it is ignored if mustAccept is not false and the accept button is visible
    hideOnSelect: false,
    showFooter: false,
    searchInFooter: false, // If true, the search will be added to the footer instead of the title
    mustAccept: false, // only applicable when there's an iconpicker-btn-accept button in the popover footer
    selectedCustomClass: 'bg-primary', // Appends this class when to the selected item
    icons: [], // list of icon classes (declared at the bottom of this script for maintainability)
    fullClassFormatter: function (val) {
      return val;
    },
    input: 'input,.iconpicker-input', // children input selector
    inputSearch: false, // use the input as a search box too?
    container: false, //  Appends the popover to a specific element. If not set, the selected element or element parent is used
    component: '.input-group-addon,.iconpicker-component', // children component jQuery selector or object, relative to the container element
    // Plugin templates:
    templates: {
      popover: '<div class="iconpicker-popover popover" role="tooltip"><div class="arrow"></div>' +
        '<div class="popover-title"></div><div class="popover-content"></div></div>',
      footer: '<div class="popover-footer"></div>',
      buttons: '<button class="iconpicker-btn iconpicker-btn-cancel btn btn-default btn-sm">Cancel</button>' +
        ' <button class="iconpicker-btn iconpicker-btn-accept btn btn-primary btn-sm">Accept</button>',
      search: '<input type="search" class="form-control iconpicker-search" placeholder="Type to filter" />',
      iconpicker: '<div class="iconpicker"><div class="iconpicker-items"></div></div>',
      iconpickerItem: '<a role="button" href="javascript:;" class="iconpicker-item"><i></i></a>',
    }
  };
  Iconpicker.batch = function (selector, method) {
    const args = Array.prototype.slice.call(arguments, 2);
    return $(selector).each(function () {
      const $inst = $(this).data('iconpicker');
      if (!!$inst) {
        $inst[method].apply($inst, args);
      }
    });
  };
  Iconpicker.prototype = {
    constructor: Iconpicker,
    options: {},
    _id: 0, // instance identifier for bind/unbind events
    _trigger(name, opts = {}) {
      // Triggers a custom event on the element with the provided name and options
      this.element.trigger({
        type: name,
        iconpickerInstance: this,
        ...opts,
      });
    },
    _createPopover() {
      const { templates, title, showFooter, animation, searchInFooter } = this.options;
      this.popover = $(templates.popover);
      // title (header)
      const _title = this.popover.find('.popover-title');
      if (title) {
        _title.append(`<div class="popover-title-text">${title}</div>`);
      }
      if (this.hasSeparatedSearchInput()) {
        const searchTemplate = templates.search;
        if (!searchInFooter) {
          _title.append(searchTemplate);
        } else if (!title) {
          _title.remove();
        }
      } else if (!title) {
        _title.remove();
      }
      // footer
      if (showFooter && !_helpers.isEmpty(templates.footer)) {
        const _footer = $(templates.footer);
        if (this.hasSeparatedSearchInput() && searchInFooter) {
          _footer.append(templates.search);
        }
        if (!_helpers.isEmpty(templates.buttons)) {
          _footer.append(templates.buttons);
        }
        this.popover.append(_footer);
      }
      if (animation) {
        this.popover.addClass('fade show');
      }
      return this.popover;
    },
    _createIconpicker() {
      const { templates, icons, fullClassFormatter, hideOnSelect, mustAccept } = this.options;
      this.iconpicker = $(templates.iconpicker);

      // Item click handler
      const itemClickFn = (event) => {
        const $item = $(event.currentTarget);
        const iconpickerValue = $item.data('iconpickerValue');

        this._trigger('iconpickerSelect', {
          iconpickerItem: $item,
          iconpickerValue: this.iconpickerValue,
        });

        this.update(iconpickerValue, mustAccept);
        if (!mustAccept) {
          this._trigger('iconpickerSelected', {
            iconpickerItem: $item,
            iconpickerValue: this.iconpickerValue,
          });
        }

        if (hideOnSelect && !mustAccept) {
          this.hide();
        }
      };

      // Generate items
      const $itemTemplate = $(templates.iconpickerItem);
      const $items = icons
        .filter((icon) => typeof icon.title === 'string')
        .map((icon) => {
          const $item = $itemTemplate.clone();
          const { title, searchTerms } = icon;

          $item.find('i').addClass(fullClassFormatter(title));
          $item.data('iconpickerValue', title).on('click.iconpicker', itemClickFn);
          $item.attr('title', `.${title}`);

          if (Array.isArray(searchTerms) && searchTerms.length > 0) {
            $item.attr('data-search-terms', searchTerms.join(' '));
          }

          return $item;
        });

      // Append items to the iconpicker
      this.iconpicker.find('.iconpicker-items').append($items);
      this.popover.find('.popover-content').append(this.iconpicker);

      return this.iconpicker;
    },
    _isEventInsideIconpicker(e) {
      const $target = $(e.target);

      return (
        $target.is(this.element) ||
        $target.hasClass('iconpicker-element') ||
        $target.closest('.iconpicker-popover').length > 0
      );
    },
    _bindElementEvents() {
      const handleKeyup = (event) => {
        const value = $(event.currentTarget).val().toLowerCase();
        this.filter(value);
      };

      const handleAccept = () => {
        const selectedItem = this.iconpicker.find('.iconpicker-selected').get(0);

        this.update(this.iconpickerValue);
        this._trigger('iconpickerSelected', {
          iconpickerItem: selectedItem,
          iconpickerValue: this.iconpickerValue,
        });

        if (!this.isInline()) {
          this.hide();
        }
      };

      const handleCancel = () => {
        if (!this.isInline()) {
          this.hide();
        }
      };

      const handleFocus = (event) => {
        this.show();
        event.stopPropagation();
      };

      const handleInputKeyup = (event) => {
        const ignoredKeys = [
          38, 40, 37, 39, 16, 17, 18, 9, 8, 91, 93, 20, 46, 186, 190, 46, 78, 188, 44, 86,
        ];

        if (!_helpers.inArray(event.keyCode, ignoredKeys)) {
          this.update();
        } else {
          this._updateFormGroupStatus(this.getValid(event.currentTarget.value) !== false);
        }

        if (this.options.inputSearch) {
          this.filter($(event.currentTarget).val().toLowerCase());
        }
      };

      // Attach events
      this.getSearchInput().on('keyup.iconpicker', handleKeyup);
      this.getAcceptButton().on('click.iconpicker', handleAccept);
      this.getCancelButton().on('click.iconpicker', handleCancel);
      this.element.on('focus.iconpicker', handleFocus);

      if (this.hasComponent()) {
        this.component.on('click.iconpicker', () => this.toggle());
      }

      if (this.hasInput()) {
        this.input.on('keyup.iconpicker', handleInputKeyup);
      }
    },
    _bindWindowEvents() {
      const namespace = `.iconpicker.inst${this._id}`;

      // Reposition popover on window resize or orientation change
      $(window).on(`resize${namespace} orientationchange${namespace}`, () => {
        if (this.popover.hasClass('in')) {
          this.updatePlacement();
        }
      });

      // Hide popover on mouseup outside the iconpicker
      if (!this.isInline()) {
        $(document).on(`mouseup${namespace}`, (e) => {
          if (!this._isEventInsideIconpicker(e)) {
            this.hide();
          }
        });
      }
    },
    _unbindElementEvents() {
      const elements = [this.popover, this.element, this.input, this.component, this.container];

      elements.forEach((el) => {
        if (el && el.off) {
          el.off('.iconpicker');
        }
      });
    },
    _unbindWindowEvents() {
      const namespace = `.iconpicker.inst${this._id}`;
      [window, document].forEach((target) => $(target).off(namespace));
    },
    updatePlacement(placement = this.options.placement, collision = this.options.collision) {
      this.options.placement = placement;
      collision = collision === true ? "flip" : collision;
      let _pos = {
        // at: Defines which position (or side) on container element to align the
        // popover element against: "horizontal vertical" alignment.
        at: "right bottom",
        // my: Defines which position (or side) on the popover being positioned to align
        // with the container element: "horizontal vertical" alignment
        my: "right top",
        // of: Which element to position against.
        of: this.hasInput() && !this.isInputGroup() ? this.input : this.container,
        // collision: When the positioned element overflows the window (or within element)
        // in some direction, move it to an alternative position.
        collision,
        // within: Element to position within, affecting collision detection.
        within: window,
      };
      const placementClasses = [
        'inline', 'topLeftCorner', 'topLeft', 'top', 'topRight', 'topRightCorner',
        'rightTop', 'right', 'rightBottom', 'bottomRightCorner', 'bottomRight',
        'bottom', 'bottomLeft', 'bottomLeftCorner', 'leftBottom', 'left', 'leftTop',
      ];

      const placementMappings = {
        topLeftCorner: { my: 'right bottom', at: 'left top' },
        topLeft: { my: 'left bottom', at: 'left top' },
        top: { my: 'center bottom', at: 'center top' },
        topRight: { my: 'right bottom', at: 'right top' },
        topRightCorner: { my: 'left bottom', at: 'right top' },
        rightTop: { my: 'left bottom', at: 'right center' },
        right: { my: 'left center', at: 'right center' },
        rightBottom: { my: 'left top', at: 'right center' },
        bottomRightCorner: { my: 'left top', at: 'right bottom' },
        bottomRight: { my: 'right top', at: 'right bottom' },
        bottom: { my: 'center top', at: 'center bottom' },
        bottomLeft: { my: 'left top', at: 'left bottom' },
        bottomLeftCorner: { my: 'right top', at: 'left bottom' },
        leftBottom: { my: 'right top', at: 'left center' },
        left: { my: 'right center', at: 'left center' },
        leftTop: { my: 'right bottom', at: 'left center' },
      };

      // Remove previous placement classes
      this.popover.removeClass(placementClasses.join(' '));

      if (typeof placement === 'object') {
        // Custom position
        return this.popover.pos({ ..._pos, ...placement });
      }

      if (placement === 'inline') {
        _pos = false;
      } else if (placementMappings[placement]) {
        Object.assign(_pos, placementMappings[placement]);
      } else {
        return false;
      }

      // Update popover styles and position
      this.popover.css({ display: placement === "inline" ? "" : "block" });

      if (_pos) {
        this.popover
          .pos(_pos)
          .css("maxWidth", $(window).width() - this.container.offset().left - 5);
      } else {
        this.popover.css({
          top: "auto",
          right: "auto",
          bottom: "auto",
          left: "auto",
          maxWidth: "none",
        });
      }

      this.popover.addClass(this.options.placement);
      return true;
    },
    _updateComponents() {
      const { iconpickerValue, options } = this;

      // Remove previous selection
      this.iconpicker
        .find('.iconpicker-item.iconpicker-selected')
        .removeClass(`iconpicker-selected ${options.selectedCustomClass}`);

      // Add selection to the current value
      if (iconpickerValue) {
        this.iconpicker
          .find(`.${options.fullClassFormatter(iconpickerValue).replace(/ /g, '.')}`)
          .parent()
          .addClass(`iconpicker-selected ${options.selectedCustomClass}`);
      }

      // Update component item
      if (this.hasComponent()) {
        const $icon = this.component.find('i');
        const formattedClass = options.fullClassFormatter(iconpickerValue);

        if ($icon.length > 0) {
          $icon.attr('class', formattedClass);
        } else {
          this.component.html(this.getHtml());
        }
      }
    },
    _updateFormGroupStatus(isValid) {
      if (!this.hasInput()) return false;
      const formGroup = this.input.parents('.form-group:first');
      // Remove form-group error class if any
      formGroup.toggleClass('has-error', isValid === false);
      return true;
    },
    getValid(val) {
      if (!_helpers.isString(val)) val = '';

      // Trim the value
      if(val !== '') {
        val = val.trim();
      }

      // Check if the value is empty or matches any icon title
      const isValid = val === '' || this.options.icons.some(icon => icon.title === val);

      return isValid ? val : false;
    },
    /**
     * Sets the internal item value and updates everything, excepting the input or element.
     * For doing so, call setSourceValue() or update() instead
     */
    setValue(val) {
      // sanitize first
      const validValue = this.getValid(val);
      if (validValue !== false) {
        this.iconpickerValue = validValue;
        this._trigger('iconpickerSetValue', { iconpickerValue: validValue });
        return this.iconpickerValue;
      }

      this._trigger('iconpickerInvalid', { iconpickerValue: val });
      return false;
    },
    getHtml() {
      const iconClass = this.options.fullClassFormatter(this.iconpickerValue);
      return `<i class="${iconClass}"></i>`;
    },
    /**
     * Calls setValue and if it's a valid item value, sets the input or element value
     */
    setSourceValue(val) {
      const validValue = this.setValue(val);
      if (validValue) {
        if (this.hasInput()) {
          this.input.val(this.iconpickerValue);
        } else {
          this.element.data('iconpickerValue', this.iconpickerValue);
        }
        this._trigger('iconpickerSetSourceValue', { iconpickerValue: validValue });
      }
      return validValue;
    },
    /**
     * Returns the input or element item value, without formatting, or defaultValue
     * if it's empty string, undefined, false or null
     * @param {type} defaultValue
     * @returns string|mixed
     */
    getSourceValue(defaultValue = this.options.defaultValue) {
      let val = this.hasInput() ? this.input.val() : this.element.data('iconpickerValue');
      // Return defaultValue if val is undefined, null, false, or an empty string
      return val || defaultValue;
    },
    hasInput: function () {
      return (this.input !== false);
    },
    isInputSearch: function () {
      return (this.hasInput() && (this.options.inputSearch === true));
    },
    isInputGroup: function () {
      return this.container.is('.input-group');
    },
    isDropdownMenu: function () {
      return this.container.is('.dropdown-menu');
    },
    hasSeparatedSearchInput: function () {
      return (this.options.templates.search !== false) && (!this.isInputSearch());
    },
    hasComponent: function () {
      return (this.component !== false);
    },
    hasContainer: function () {
      return (this.container !== false);
    },
    getAcceptButton: function () {
      return this.popover.find('.iconpicker-btn-accept');
    },
    getCancelButton: function () {
      return this.popover.find('.iconpicker-btn-cancel');
    },
    getSearchInput: function () {
      return this.popover.find('.iconpicker-search');
    },
    filter(filterText) {
      if (_helpers.isEmpty(filterText)) {
        this.iconpicker.find('.iconpicker-item').show();
        return $();
      }
      const found = [];
      const regex = (() => {
        try {
          return new RegExp(`(^|\\W)${filterText.toLowerCase()}`, 'g');
        } catch {
          return null;
        }
      })();

      this.iconpicker.find('.iconpicker-item').each(function () {
        const $item = $(this);
        const title = $item.attr('title')?.toLowerCase() || '';
        const searchTerms = $item.attr('data-search-terms')?.toLowerCase() || '';
        const combinedText = `${title} ${searchTerms}`;

        if (regex && regex.test(combinedText)) {
          found.push($item);
          $item.show();
        } else {
          $item.hide();
        }
      });
      return found;
    },
    show() {
      if (this.popover.hasClass('in')) {
        return false;
      }
      // Hide other non-inline pickers
      $.iconpicker.batch(
        $('.iconpicker-popover.in:not(.inline)').not(this.popover),
        'hide'
      );
      this._trigger('iconpickerShow', { iconpickerValue: this.iconpickerValue });
      this.updatePlacement();
      this.popover.addClass('in');
      const animationDuration = this.options.animation ? 300 : 1;
      setTimeout(() => {
        this.popover.css('display', this.isInline() ? '' : 'block');
        this._trigger('iconpickerShown', { iconpickerValue: this.iconpickerValue });
      }, animationDuration);
    },
    hide() {
      if (!this.popover.hasClass('in')) {
        return false;
      }
      this._trigger('iconpickerHide', { iconpickerValue: this.iconpickerValue });
      this.popover.removeClass('in');
      const animationDuration = this.options.animation ? 300 : 1;
      setTimeout(() => {
        this.popover.css('display', 'none');
        this.getSearchInput().val(''); // Clear search input
        this.filter(''); // Clear filter
        this._trigger('iconpickerHidden', { iconpickerValue: this.iconpickerValue });
      }, animationDuration);
    },
    toggle() {
      this.popover.is(':visible') ? this.hide() : this.show(true);
    },
    update(val, updateOnlyInternal = false) {
      val = val || this.getSourceValue(this.iconpickerValue);
      // Reads the input or element value again and tries to update the plugin
      // fallback to the current selected item value
      this._trigger('iconpickerUpdate', { iconpickerValue: this.iconpickerValue });
      val = updateOnlyInternal ? this.setValue(val) : this.setSourceValue(val);
      if (!updateOnlyInternal) {
        this._updateFormGroupStatus(val !== false);
      }

      if (val !== false) {
        this._updateComponents();
      }

      // Trigger updated event
      this._trigger('iconpickerUpdated', { iconpickerValue: this.iconpickerValue });
      return val;
    },
    destroy() {
      this._trigger('iconpickerDestroy', { iconpickerValue: this.iconpickerValue });
      this.element.removeData('iconpicker').removeData('iconpickerValue').removeClass('iconpicker-element');
      // Unbinds events and resets everything to the initial state, including component mode
      this._unbindElementEvents();
      this._unbindWindowEvents();
      this.popover?.remove();
      this._trigger('iconpickerDestroyed', { iconpickerValue: this.iconpickerValue });
    },
    disable() {
      if (!this.hasInput()) return false;

      this.input.prop('disabled', true);
      return true;
    },
    enable() {
      if (!this.hasInput()) return false;

      this.input.prop('disabled', false);
      return true;
    },
    isDisabled() {
      return this.hasInput() && this.input.prop('disabled') === true;
    },
    isInline() {
      return this.options.placement === 'inline' || this.popover.hasClass('inline');
    }
  };
  $.iconpicker = Iconpicker;
  $.fn.iconpicker = function (options) {
    return this.each(function () {
      if (!$.data(this, "iconpicker")) {
        $.data(this, "iconpicker", new Iconpicker(this, $.isPlainObject(options) ? options : {}));
      }
    });
  };
});

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc