JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css">
<link rel="stylesheet" href="https://bootswatch.com/4/darkly/bootstrap.min.css">

<div class="container mt-3">

  <h4 class="pb-1"><span>NeonSelect</span> <span class="small text-muted">Select, multi-select, tags, autocomplete and @mentions ... all in one! <br></span></h4>
  <p>ES6, only 1500 lines, no dependancies. Inspired by selectize, bootstrap-select, mention and At.js</p>
  <p>What used to require three or more seperate plugins - can now be achieved with this one lightweight vanilla Javascript plugin</p>
  <p>Essentially this plugin combines selectize, bootstrap-select, At.js and jQuery UI autocomplete into one plugin - perfect for progressive web apps where you want to limit the size of your scripts.</p>
  <p>From simple select boxes to Twitter style hashtags in a commenting system - we've got you covered</p>
  <h5>Select</h5>
  <div id="tags-test"></div>
  <hr>
  <h5>Select <small>(with select input)</small></h5>
  <select id="tags-test-select"></select>
  <hr>
  <h5>Multi-select</h5>
  <div id="tags-test2"></div>
  <hr>
  <h5>Mentions</h5>
  <div id="tags-test3"></div>
  <hr>
  <h5>Autocomplete</h5>
  <p>Similar to autocomplete by jQuery UI. It is like our single select box except the dropdown only appears once you type 3 or more letters</p>
  <div id="autocomplete-test"></div>
  <hr>
  <h5>Dropdown always open</h5>
  <p>I told you this thing was pretty flexible! You can force the dropdown to always be open</p>
  <div class="card bg-warning w-50">
    <div class="card-header bg-warning">
      <i class="fa fa-search"></i> Search
    </div>
    <div class="card-body">
       <div id="autocomplete-test2"></div>
        <div id="autocomplete-dropdown"></div>
    </div>
  </div>
  
  <hr>
  <h6>Features</h6>
  <ul>
    <li>Extremely customizable on a per resource basis</li>
    <li>Multiple triggers and data sources</li>
    <li>Override item and option rendering</li>
   ...

SCSS

$neon-select-margin-item-x: 3px !default;
$neon-select-margin-item-y: 3px !default;
$neon-select-padding-x: 8px !default;
$neon-select-padding-y: 8px !default;
$neon-select-padding-item-x: 6px !default;
$neon-select-padding-item-y: 2px !default;

$neon-select-color-text: #303030 !default;
$neon-select-width-item-border: 0 !default;

$neon-select-color-item: #f2f2f2 !default;
$neon-select-color-item-text: $neon-select-color-text !default;
$neon-select-color-item-border: #d0d0d0 !default;
$neon-select-color-item-active: rgb(0, 123, 255) !default;
$neon-select-color-item-active-text: white !default;
$neon-select-color-item-active-border: #cacaca !default;

.neon-select-control .item {
  cursor: default;
  margin: 0 $neon-select-margin-item-x $neon-select-margin-item-y 0;
  padding: $neon-select-padding-item-y $neon-select-padding-item-x;
  background: $neon-select-color-item;
  color: $neon-select-color-item-text;
  border: $neon-select-width-item-border solid $neon-select-color-item-border;
  display:inline-block;
  border-radius:4px;
  -webkit-user-select:none;
  -moz-user-select: none;
  user-select:none;
  
  
  &.highlight {
    background: $neon-select-color-item-active;
    color: $neon-select-color-item-active-text;
    border: $neon-select-width-item-border solid $neon-select-color-item-active-border;
    caret-color: transparent;
  }
  
  &.editing {
    background:#333;
    color:white;
    .remove {
      display:none;
    }
  }
  
  &.editing[data-resource="hashtag"] {
    background:#666;
    color:white;
  }
  &.highlight[data-resource="users"] {
    background:#AAB2BD;
  }
  &.highlight[data-resource="hashtag"] {
    background:#48CFAD;
  }
  
  .remove {
    &:hover {
      color:#999;
    }
    cursor:pointer;
    text-align: center;
    margin-left:6px;
    //vertical-align: middle;
    border-left: 1px solid #bbb;
  }
  
}

.neon-select-control.btn {
  position:relative;
  &:after {
    position:absolute;
    width:0;
    height:0;
    content:...

JavaScript

const htmlToElement = function (html) {
	const template = document.createElement('template');
	template.innerHTML = html;
	return template.content.firstElementChild;
}

const extend = function () { //Source: https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
	// Variables
	let extended = {};
	let deep = false;
	let i = 0;
	const length = arguments.length;

	// Check if a deep merge
	if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') {
		deep = arguments[0];
		i++;
	}

	// Merge the object into the extended object
	const merge = function (obj) {
		for (let prop in obj) {
			if (Object.prototype.hasOwnProperty.call(obj, prop)) {
				// If deep merge and property is an object, merge properties
				if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
					extended[prop] = extend(true, extended[prop], obj[prop]);
				} else {
					extended[prop] = obj[prop];
				}
			}
		}
	};

	// Loop through each object and conduct a merge
	for (; i < length; i++) {
		let obj = arguments[i];
		merge(obj);
	}

	return extended;

};

const makeDelay = function(ms) {
    let timer = 0;
    return function(callback) {
        clearTimeout(timer);
        timer = setTimeout(callback, ms);
    };
};

const getOffset = function(el, win = false) {
	el = el.getBoundingClientRect();
	return (!win) ? copyBoundRect(el) : {
		left:el.left + window.scrollX,
		top:el.top + window.scrollY,
		right:el.right + window.scrollX,
		bottom:el.bottom + window.scrollY,
		x:el.left + window.scrollX,
		y:el.top + window.scrollY,
		width:el.width,
		height:el.height
	};
};

const copyBoundRect = function(rect) {
	/*Convert getBoundingClientRect to a regular object*/
	return {
		x:rect.x,
		y:rect.y,
		top:rect.top,
		left:rect.left,
		bottom:rect.bottom,
		right:rect.right,
		width:rect.width,
		height:rect.height
  };
};

const convertPos = function(pos) {

	let textPositions = [{
  	top:0,
    middle:0.5,
    bottom:1
  },{
  	left:0,
   ...