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,
...