Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

datalist-polyfill

Package Overview
Dependencies
Maintainers
1
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

datalist-polyfill - npm Package Compare versions

Comparing version 1.15.1 to 1.16.0

2

bower.json
{
"name": "datalist-polyfill",
"description": "An extremely lightweight and library-dependency-free vanilla JavaScript datalist polyfill.",
"version": "1.15.1",
"version": "1.16.0",
"homepage": "https://github.com/mfranzke/datalist-polyfill",

@@ -6,0 +6,0 @@ "authors": [{

@@ -9,2 +9,13 @@ # Changelog

## [1.16.0] - 2018-06-27
### Added
- Linting as well as security: prettier, xo, codacy
- And their badges
### Changed
Made a lot of code changes in relation to what the previously mentioned linters reported. (e.g. #GH-23)
### Security
Made some code changes in relation to what the previously mentioned linters reported. (e.g. #GH-21, #GH-22)
## [1.15.1] - 2018-06-22

@@ -11,0 +22,0 @@ ### Fixed

/*
* datalist-polyfill.js - https://github.com/mfranzke/datalist-polyfill
* Datalist polyfill - https://github.com/mfranzke/datalist-polyfill
* @license Copyright(c) 2017 by Maximilian Franzke

@@ -13,441 +13,486 @@ * Supported by Christian, Johannes, @mitchhentges, @mertenhanisch, @ailintom, @Kravimir, @mischah, @hryamzik, @ottoville, @IceCreamYou, @wlekin, @eddr and @beebee1987 - many thanks for that !

(function() {
'use strict';
'use strict';
// feature detection - let's break here, if it's even already supported
if ('list' in document.createElement('input') && !!( document.createElement('datalist') && window.HTMLDataListElement)) {
return false;
}
// Feature detection - let's break here, if it's even already supported
if (
'list' in document.createElement('input') &&
Boolean(document.createElement('datalist') && window.HTMLDataListElement)
) {
return false;
}
// emulate the two properties regarding the datalist and input elements
// list property / https://developer.mozilla.org/en/docs/Web/API/HTMLInputElement
(function(constructor) {
if (constructor && constructor.prototype && constructor.prototype.list === undefined) {
Object.defineProperty(constructor.prototype, 'list', {
get: function() {
if (typeof( this ) === 'object' && this instanceof constructor) {
var list = this.getAttribute('list');
// Emulate the two properties regarding the datalist and input elements
// list property / https://developer.mozilla.org/en/docs/Web/API/HTMLInputElement
(function(constructor) {
if (
constructor &&
constructor.prototype &&
constructor.prototype.list === null
) {
Object.defineProperty(constructor.prototype, 'list', {
get: function() {
return typeof this === 'object' && this instanceof constructor
? document.getElementById(this.getAttribute('list'))
: null;
}
});
}
})(window.HTMLInputElement);
// Options property / https://developer.mozilla.org/en/docs/Web/API/HTMLDataListElement
(function(constructor) {
if (
constructor &&
constructor.prototype &&
constructor.prototype.options === null
) {
Object.defineProperty(constructor.prototype, 'options', {
get: function() {
return typeof this === 'object' && this instanceof constructor
? this.getElementsByTagName('option')
: null;
}
});
}
})(window.HTMLElement);
return document.getElementById(list);
}
return null;
}
});
}
})(window.HTMLInputElement);
// options property / https://developer.mozilla.org/en/docs/Web/API/HTMLDataListElement
(function(constructor) {
if (constructor && constructor.prototype && constructor.prototype.options === undefined) {
Object.defineProperty(constructor.prototype, 'options', {
get: function() {
if (typeof( this ) === 'object' && this instanceof constructor) {
return this.getElementsByTagName('option');
}
}
});
}
})(window.HTMLElement);
// Identify whether a select multiple is feasible
var touched = false,
// Speaking variables for the different keycodes
keyENTER = 13,
keyESC = 27,
keyUP = 38,
keyDOWN = 40,
// Defining the text / value seperator for displaying the value and text values ...
textValueSeperator = ' / ',
// ... and defining the different input types that are supported by this polyfill
supportedTypes = ['text', 'email', 'number', 'search', 'tel', 'url'],
// Classes for elements
classNameInput = 'polyfilled',
classNamePolyfillingSelect = 'polyfilling';
// identify whether a select multiple is feasible
var touched = false,
// Differentiate for touch interactions, adapted by https://medium.com/@david.gilbertson/the-only-way-to-detect-touch-with-javascript-7791a3346685
window.addEventListener('touchstart', function onFirstTouch() {
touched = true;
// speaking variables for the different keycodes
keyENTER = 13,
keyESC = 27,
keyUP = 38,
keyDOWN = 40,
window.removeEventListener('touchstart', onFirstTouch);
});
// defining the text / value seperator for displaying the value and text values
textValueSeperator = ' / ',
// For observing any changes to the option elements within the datalist elements, define MutationObserver initially
var MutationObserver =
window.MutationObserver || window.WebKitMutationObserver,
obs;
// and defining the different input types that are supported by this polyfill
supportedTypes = ['text', 'email', 'number', 'search', 'tel', 'url'],
// Define a new observer
if (typeof MutationObserver !== 'undefined') {
obs = new MutationObserver(function(mutations) {
var datalistNeedsAnUpdate = false;
// classes for elements
classNameInput = 'polyfilled',
classNamePolyfillingSelect = 'polyfilling';
// Look through all mutations that just occured
mutations.forEach(function(mutation) {
// Look through all added nodes of this mutation
for (var j = 0; j < mutation.addedNodes.length; ++j) {
if (mutation.target.tagName.toLowerCase() === 'datalist') {
datalistNeedsAnUpdate = mutation.target;
}
}
});
// differentiate for touch interactions, adapted by https://medium.com/@david.gilbertson/the-only-way-to-detect-touch-with-javascript-7791a3346685
window.addEventListener('touchstart', function onFirstTouch() {
touched = true;
if (datalistNeedsAnUpdate) {
var input = document.querySelector(
'[list="' + datalistNeedsAnUpdate.id + '"]'
);
window.removeEventListener('touchstart', onFirstTouch);
});
if (input.value !== '') {
var dataList = datalistNeedsAnUpdate;
// Prepare the options and toggle the visiblity afterwards
toggleVisibility(
prepOptions(dataList, input),
dataList.getElementsByClassName(classNamePolyfillingSelect)[0]
);
}
}
});
}
// for observing any changes to the option elements within the datalist elements, define MutationObserver initially
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
// Function regarding the inputs interactions
var inputInputList = function(event) {
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase(),
dataList = eventTarget.list;
// define a new observer
if (typeof(MutationObserver) !== 'undefined') {
var obs = new MutationObserver(function(mutations) {
var datalistNeedsAnUpdate = false;
// Check for whether the events target was an input datalist and still check for an existing instance
if (
eventTargetTagName &&
eventTargetTagName === 'input' &&
dataList !== null
) {
var dataListSelect =
dataList.getElementsByClassName(classNamePolyfillingSelect)[0] ||
setUpPolyfillingSelect(eventTarget, dataList);
// look through all mutations that just occured
for(var i=0; i<mutations.length; ++i) {
// Still check for an existing instance
if (dataListSelect !== null) {
var visible = false,
inputValue = eventTarget.value,
keyOpen = event.keyCode === keyUP || event.keyCode === keyDOWN;
// look through all added nodes of this mutation
for(var j=0; j<mutations[i].addedNodes.length; ++j) {
if (mutations[i].target.tagName.toLowerCase() === 'datalist') {
datalistNeedsAnUpdate = mutations[i].target;
}
}
}
// On an ESC or ENTER key press within the input, let's break here and afterwards hide the datalist select, and if the input contains a value
if (
event.keyCode !== keyESC &&
event.keyCode !== keyENTER &&
(inputValue !== '' || keyOpen)
) {
// Prepare the options
if (prepOptions(dataList, eventTarget)) {
visible = true;
}
if (datalistNeedsAnUpdate) {
var input = document.querySelector('[list="' + datalistNeedsAnUpdate.id + '"]');
var dataListSelectOptionsLength = dataListSelect.options.length,
firstEntry = 0,
lastEntry = dataListSelectOptionsLength - 1;
if (input.value !== '') {
var dataList = datalistNeedsAnUpdate;
if (touched) {
// Preselect best fitting index
dataListSelect.selectedIndex = firstEntry;
} else if (dataListSelect.selectedIndex === -1 && keyOpen) {
dataListSelect.selectedIndex =
event.keyCode === keyUP ? lastEntry : firstEntry;
}
// prepare the options and toggle the visiblity afterwards
toggleVisibility(prepOptions(dataList, input), dataList.getElementsByClassName(classNamePolyfillingSelect)[0]);
}
}
});
}
// On arrow up or down keys, focus the select
if (keyOpen) {
dataListSelect.focus();
}
}
// function regarding the inputs interactions
var inputInputList = function(event) {
// Toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, dataListSelect);
}
}
};
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase(),
dataList = eventTarget.list;
// Function for preparing and sorting the options/suggestions
var prepOptions = function(dataList, input) {
if (typeof obs !== 'undefined') {
obs.disconnect();
}
// check for whether the events target was an input datalist and still check for an existing instance
if (eventTargetTagName && eventTargetTagName === 'input' && dataList !== null) {
var dataListSelect =
dataList.getElementsByClassName(classNamePolyfillingSelect)[0] ||
setUpPolyfillingSelect(input, dataList),
dataListOptions = dataList.querySelectorAll('option:not(:disabled)'),
inputValue = input.value,
newSelectValues = document.createDocumentFragment(),
disabledValues = document.createDocumentFragment();
var dataListSelect = dataList.getElementsByClassName(classNamePolyfillingSelect)[0] || setUpPolyfillingSelect(eventTarget, dataList);
// In case of type=email and multiple attribute, we would need grab the last piece
if (input.type === 'email' && input.multiple) {
inputValue = inputValue.substring(inputValue.lastIndexOf(',') + 1);
}
// still check for an existing instance
if (dataListSelect !== undefined) {
// ... create an array out of the options list
var nodeArray = Array.prototype.slice.call(dataListOptions);
var visible = false;
// ... sort all entries and
nodeArray
.sort(function(a, b) {
return a.value.localeCompare(b.value);
})
.forEach(function(opt) {
var optionValue = opt.value;
// on an ESC or ENTER key press within the input, let's break here and afterwards hide the datalist select
if (event.keyCode !== keyESC && event.keyCode !== keyENTER) {
// ... put this option into the fragment that is meant to get inserted into the select
// "Each option element that is a descendant of the datalist element, that is not disabled, and whose value is a string that isn't the empty string, represents a suggestion. Each suggestion has a value and a label." (W3C)
if (
optionValue !== '' &&
optionValue.toLowerCase().indexOf(inputValue.toLowerCase()) !== -1 &&
opt.disabled === false
) {
var label = opt.getAttribute('label'),
text = opt.text,
textOptionPart = text.substr(
0,
optionValue.length + textValueSeperator.length
),
optionPart = optionValue + textValueSeperator;
var inputValue = eventTarget.value,
keyOpen = (event.keyCode === keyUP || event.keyCode === keyDOWN);
// The innertext should be value / text in case they are different
if (
text &&
!label &&
text !== optionValue &&
textOptionPart !== optionPart
) {
opt.innerText = optionValue + textValueSeperator + text;
} else if (!opt.text) {
// Manipulating the option inner text, that would get displayed
opt.innerText = label || optionValue;
}
// if the input contains a value, than ...
if (inputValue !== '' || keyOpen) {
newSelectValues.appendChild(opt);
} else {
// ... or put this option that isn't relevant to the users into the fragment that is supposed to get inserted outside of the select
disabledValues.appendChild(opt);
}
});
// prepare the options
if (prepOptions(dataList, eventTarget)) {
visible = true;
}
// Input the options fragment into the datalists select
dataListSelect.appendChild(newSelectValues);
var dataListSelectOptionsLength = dataListSelect.options.length,
firstEntry = 0,
lastEntry = dataListSelectOptionsLength - 1;
var dataListSelectOptionsLength = dataListSelect.options.length;
if (touched) {
// preselect best fitting index
dataListSelect.selectedIndex = firstEntry;
dataListSelect.size =
dataListSelectOptionsLength > 10 ? 10 : dataListSelectOptionsLength;
dataListSelect.multiple = !touched && dataListSelectOptionsLength < 2;
} else if (dataListSelect.selectedIndex === -1) {
// Input the unused options as siblings next to the select - and differentiate in between the regular, and the IE9 fix syntax upfront
var dataListAppend =
dataList.getElementsByClassName('ie9_fix')[0] || dataList;
switch (event.keyCode) {
case keyUP:
dataListSelect.selectedIndex = lastEntry;
break;
case keyDOWN:
dataListSelect.selectedIndex = firstEntry;
break;
}
}
dataListAppend.appendChild(disabledValues);
// on arrow up or down keys, focus the select
if (keyOpen) {
if (typeof obs !== 'undefined') {
obs.observe(dataList, {
childList: true
});
}
dataListSelect.focus();
}
return dataListSelectOptionsLength;
};
}
}
// Focus or blur events
var changesInputList = function(event) {
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase(),
dataList = eventTarget.list;
// toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, dataListSelect);
}
}
};
// Check for whether the events target was an input datalist and still check for an existing instance
if (
eventTargetTagName &&
eventTargetTagName === 'input' &&
dataList !== null
) {
var eventType = event.type,
// Creating the select if there's no instance so far (e.g. because of that it hasn't been handled or it has been dynamically inserted)
dataListSelect =
dataList.getElementsByClassName(classNamePolyfillingSelect)[0] ||
setUpPolyfillingSelect(eventTarget, dataList),
// Either have the select set to the state to get displayed in case of that it would have been focused or because it's the target on the inputs blur - and check for general existance of any option as suggestions
visible =
dataListSelect &&
dataListSelect.querySelector('option:not(:disabled)') &&
((eventType === 'focusin' && eventTarget.value !== '') ||
(event.relatedTarget && event.relatedTarget === dataListSelect));
// function for preparing and sorting the options/suggestions
var prepOptions = function(dataList, input) {
// Test for whether this input has already been enhanced by the polyfill
if (
' ' + eventTarget.className + ' '.indexOf(' ' + classNameInput + ' ') <
0
) {
// We'd like to prevent autocomplete on the input datalist field
eventTarget.setAttribute('autocomplete', 'off');
if (typeof(obs) !== 'undefined') {
obs.disconnect();
}
// WAI ARIA attributes
eventTarget.setAttribute('role', 'textbox');
eventTarget.setAttribute('aria-haspopup', 'true');
eventTarget.setAttribute('aria-autocomplete', 'list');
eventTarget.setAttribute('aria-owns', eventTarget.getAttribute('list'));
var dataListSelect = dataList.getElementsByClassName(classNamePolyfillingSelect)[0] || setUpPolyfillingSelect(input, dataList),
dataListOptions = dataList.querySelectorAll('option:not(:disabled)'),
inputValue = input.value,
newSelectValues = document.createDocumentFragment(),
disabledValues = document.createDocumentFragment();
// Bind the keyup event on the related datalists input
if (eventType === 'focusin') {
eventTarget.addEventListener('keyup', inputInputList);
// in case of type=email and multiple attribute, we would need to split the inputs value into pieces
if (input.type === 'email' && input.multiple) {
var multipleEntries = inputValue.split(','),
relevantIndex = multipleEntries.length - 1;
eventTarget.addEventListener('focusout', changesInputList, true);
} else if (eventType === 'blur') {
eventTarget.removeEventListener('keyup', inputInputList);
inputValue = multipleEntries[relevantIndex].trim();
}
eventTarget.removeEventListener('focusout', changesInputList, true);
}
// ... create an array out of the options list
var nodeArray = Array.prototype.slice.call(dataListOptions);
// Add class for identifying that this input is even already being polyfilled
eventTarget.className += ' ' + classNameInput;
}
// ... sort all entries and
nodeArray.sort(function(a, b) {
return a.value.localeCompare(b.value);
})
.forEach(function(opt) {
var optionValue = opt.value;
// Toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, dataListSelect);
}
};
// ... put this option into the fragment that is meant to get inserted into the select
// "Each option element that is a descendant of the datalist element, that is not disabled, and whose value is a string that isn't the empty string, represents a suggestion. Each suggestion has a value and a label." (W3C)
if (optionValue !== '' && optionValue.toLowerCase()
.indexOf(inputValue.toLowerCase()) !== -1 && opt.disabled === false) {
// Define function for setting up the polyfilling select
var setUpPolyfillingSelect = function(input, dataList) {
// Check for whether it's of one of the supported input types defined at the beginning
if (supportedTypes.indexOf(input.type) > -1) {
// Still check for an existing instance
if (dataList !== null) {
var message = dataList.title,
rects = input.getClientRects(),
// Measurements
inputStyles = window.getComputedStyle(input),
inputStyleMarginRight = parseFloat(
inputStyles.getPropertyValue('margin-right')
),
inputStyleMarginLeft = parseFloat(
inputStyles.getPropertyValue('margin-left')
),
dataListSelect = document.createElement('select');
var label = opt.getAttribute('label'),
text = opt.text,
textOptionPart = text.substr(0, optionValue.length + textValueSeperator.length),
optionPart = optionValue + textValueSeperator;
// Setting a class for easier selecting that select afterwards
dataListSelect.setAttribute('class', classNamePolyfillingSelect);
// the innertext should be value / text in case they are different
if (text && !label && text !== optionValue && textOptionPart !== optionPart) {
opt.innerText = optionValue + textValueSeperator + text;
// Set general styling related definitions
dataListSelect.style.position = 'absolute';
} else if (!opt.text) {
// manipulating the option inner text, that would get displayed
opt.innerText = label || optionValue;
}
// Initially hiding the datalist select
toggleVisibility(false, dataListSelect);
newSelectValues.appendChild(opt);
} else {
// ... or put this option that isn't relevant to the users into the fragment that is supposed to get inserted outside of the select
disabledValues.appendChild(opt);
}
});
// WAI ARIA attributes
dataListSelect.setAttribute('aria-live', 'polite');
dataListSelect.setAttribute('role', 'listbox');
if (!touched) {
dataListSelect.setAttribute('aria-multiselectable', 'false');
}
// input the options fragment into the datalists select
dataListSelect.appendChild(newSelectValues);
// The select should get positioned underneath the input field ...
if (inputStyles.getPropertyValue('display') === 'block') {
dataListSelect.style.marginTop =
'-' + inputStyles.getPropertyValue('margin-bottom');
} else {
if (inputStyles.getPropertyValue('direction') === 'rtl') {
dataListSelect.style.marginRight =
'-' + (rects[0].width + inputStyleMarginLeft) + 'px';
} else {
dataListSelect.style.marginLeft =
'-' + (rects[0].width + inputStyleMarginRight) + 'px';
}
var dataListSelectOptionsLength = dataListSelect.options.length;
dataListSelect.style.marginTop =
parseInt(
rects[0].height + (input.offsetTop - dataList.offsetTop),
10
) + 'px';
}
dataListSelect.size = (dataListSelectOptionsLength > 10) ? 10 : dataListSelectOptionsLength;
dataListSelect.multiple = (!touched && dataListSelectOptionsLength < 2);
// Set the polyfilling selects border-radius equally as the one by the polyfilled input
dataListSelect.style.borderRadius = inputStyles.getPropertyValue(
'border-radius'
);
dataListSelect.style.minWidth = rects[0].width + 'px';
// input the unused options as siblings next to the select - and differentiate in between the regular, and the IE9 fix syntax upfront
var dataListAppend = dataList.getElementsByClassName('ie9_fix')[0] || dataList;
if (touched) {
var messageElement = document.createElement('option');
dataListAppend.appendChild(disabledValues);
// ... and it's first entry should contain the localized message to select an entry
messageElement.innerText = message;
// ... and disable this option, as it shouldn't get selected by the user
messageElement.disabled = true;
// ... and assign a dividable class to it
messageElement.setAttribute('class', 'message');
// ... and finally insert it into the select
dataListSelect.appendChild(messageElement);
}
if (typeof(obs) !== 'undefined') {
obs.observe(dataList, {
childList: true
});
}
// Add select to datalist element ...
dataList.appendChild(dataListSelect);
return dataListSelectOptionsLength;
// ... and our upfollowing function to the change event
};
if (touched) {
dataListSelect.addEventListener('change', changeDataListSelect);
} else {
dataListSelect.addEventListener('click', changeDataListSelect);
}
dataListSelect.addEventListener('blur', changeDataListSelect);
dataListSelect.addEventListener('keyup', changeDataListSelect);
// focus or blur events
var changesInputList = function(event) {
return dataListSelect;
}
}
};
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase(),
dataList = eventTarget.list;
// Function regarding changes to the datalist polyfilling created select
var changeDataListSelect = function(event) {
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase();
// check for whether the events target was an input datalist and whether it's of one of the supported input types defined above and still check for an existing instance
if (eventTargetTagName && eventTargetTagName === 'input' && dataList !== null) {
// Check for whether the events target was a select or an option
if (
eventTargetTagName &&
(eventTargetTagName === 'select' || eventTargetTagName === 'option')
) {
var select =
eventTargetTagName === 'select'
? eventTarget
: eventTarget.parentNode,
datalist = select.parentNode,
message = datalist.title,
eventType = event.type,
// ENTER and ESC
visible =
eventType === 'keyup' &&
(event.keyCode !== keyENTER && event.keyCode !== keyESC);
var eventType = event.type,
// creating the select if there's no instance so far (e.g. because of that it hasn't been handled or it has been dynamically inserted)
dataListSelect = dataList.getElementsByClassName(classNamePolyfillingSelect)[0] || setUpPolyfillingSelect(eventTarget, dataList),
// either have the select set to the state to get displayed in case of that it would have been focused or because it's the target on the inputs blur - and check for general existance of any option as suggestions
visible = (dataListSelect && dataListSelect.querySelector('option:not(:disabled)') && ((eventType === 'focusin' && eventTarget.value !== '') || (event.relatedTarget && event.relatedTarget === dataListSelect)));
// Change or mouseup event or ENTER key
if (
eventType === 'change' ||
eventType === 'click' ||
(eventType === 'keyup' && event.keyCode === keyENTER)
) {
var list = datalist.id,
inputList = document.querySelector('input[list="' + list + '"]'),
selectValue = select.value;
// test for whether this input has already been enhanced by the polyfill
if (!new RegExp(' ' + classNameInput + ' ').test(' ' + eventTarget.className + ' ')) {
// we'd like to prevent autocomplete on the input datalist field
eventTarget.setAttribute('autocomplete', 'off');
// Input the selects value into the input on a change within the list
if (
inputList !== null &&
typeof selectValue !== 'undefined' &&
selectValue.length > 0 &&
selectValue !== message
) {
var lastSeperator, evt;
// WAI ARIA attributes
eventTarget.setAttribute('role', 'textbox');
eventTarget.setAttribute('aria-haspopup', 'true');
eventTarget.setAttribute('aria-autocomplete', 'list');
eventTarget.setAttribute('aria-owns', eventTarget.getAttribute('list'));
// In case of type=email and multiple attribute, we need to set up the resulting inputs value differently
inputList.value =
inputList.type === 'email' &&
inputList.multiple &&
(lastSeperator = inputList.value.lastIndexOf(',')) > -1
? inputList.value.slice(0, lastSeperator) + ',' + selectValue
: (inputList.value = selectValue);
// bind the keyup event on the related datalists input
switch (eventType) {
case 'focusin':
eventTarget.addEventListener('keyup', inputInputList);
// Create and dispatch the input event; divided for IE9 usage
if (typeof Event === 'function') {
evt = new Event('input', {
bubbles: true
});
} else {
evt = document.createEvent('Event');
evt.initEvent('input', true, false);
}
inputList.dispatchEvent(evt);
eventTarget.addEventListener('focusout', changesInputList, true);
break;
case 'blur':
eventTarget.removeEventListener('keyup', inputInputList);
// Finally focusing the input, as other browser do this as well
inputList.focus();
eventTarget.removeEventListener('focusout', changesInputList, true);
break;
}
// Set the visibility to false afterwards, as we're done here
visible = false;
}
}
// add class for identifying that this input is even already being polyfilled
eventTarget.className += ' ' + classNameInput;
}
// Toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, select);
}
};
// toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, dataListSelect);
}
};
// Toggle the visibility of the datalist select
var toggleVisibility = function(visible, dataListSelect) {
if (visible) {
dataListSelect.removeAttribute('hidden');
} else {
dataListSelect.setAttributeNode(document.createAttribute('hidden'));
}
dataListSelect.setAttribute('aria-hidden', (!visible).toString());
};
// define function for setting up the polyfilling select
var setUpPolyfillingSelect = function(input, dataList) {
if (supportedTypes.indexOf(input.type) > -1) {
// still check for an existing instance
if (dataList !== null) {
var message = dataList.title,
rects = input.getClientRects(),
// measurements
inputStyles = window.getComputedStyle(input),
inputStyleMarginRight = parseFloat(inputStyles.getPropertyValue('margin-right')),
inputStyleMarginLeft = parseFloat(inputStyles.getPropertyValue('margin-left')),
dataListSelect = document.createElement('select');
// setting a class for easier selecting that select afterwards
dataListSelect.setAttribute('class', classNamePolyfillingSelect);
// set general styling related definitions
dataListSelect.style.position = 'absolute';
// initially hiding the datalist select
toggleVisibility(false, dataListSelect);
// WAI ARIA attributes
dataListSelect.setAttribute('aria-live', 'polite');
dataListSelect.setAttribute('role', 'listbox');
if (!touched) {
dataListSelect.setAttribute('aria-multiselectable', 'false');
}
// the select should get positioned underneath the input field ...
if (inputStyles.getPropertyValue('display') === 'block') {
dataListSelect.style.marginTop = '-' + inputStyles.getPropertyValue('margin-bottom');
} else {
if (inputStyles.getPropertyValue('direction') === 'rtl') {
dataListSelect.style.marginRight = '-' + (rects[0].width + inputStyleMarginLeft) + 'px';
} else {
dataListSelect.style.marginLeft = '-' + (rects[0].width + inputStyleMarginRight) + 'px';
}
dataListSelect.style.marginTop = parseInt((rects[0].height + (input.offsetTop - dataList.offsetTop)), 10) + 'px';
}
// set the polyfilling selects border-radius equally as the one by the polyfilled input
dataListSelect.style.borderRadius = inputStyles.getPropertyValue('border-radius');
dataListSelect.style.minWidth = rects[0].width + 'px';
if (touched) {
var messageElement = document.createElement('option');
// ... and it's first entry should contain the localized message to select an entry
messageElement.innerText = message;
// ... and disable this option, as it shouldn't get selected by the user
messageElement.disabled = true;
// ... and assign a dividable class to it
messageElement.setAttribute('class', 'message');
// ... and finally insert it into the select
dataListSelect.appendChild(messageElement);
}
// add select to datalist element ...
dataList.appendChild(dataListSelect);
// ... and our upfollowing function to the change event
if (touched) {
dataListSelect.addEventListener('change', changeDataListSelect);
} else {
dataListSelect.addEventListener('click', changeDataListSelect);
}
dataListSelect.addEventListener('blur', changeDataListSelect);
dataListSelect.addEventListener('keyup', changeDataListSelect);
return dataListSelect;
}
}
};
// function regarding changes to the datalist polyfilling created select
var changeDataListSelect = function(event) {
var eventTarget = event.target,
eventTargetTagName = eventTarget.tagName.toLowerCase();
// check for whether the events target was a select or an option
if (eventTargetTagName && (eventTargetTagName === 'select' || eventTargetTagName === 'option')) {
var select = (eventTargetTagName === 'select') ? eventTarget : eventTarget.parentNode,
datalist = select.parentNode,
message = datalist.title,
eventType = event.type,
// ENTER and ESC
visible = (eventType === 'keyup' && (event.keyCode !== keyENTER && event.keyCode !== keyESC));
// change or mouseup event or ENTER key
if (eventType === 'change' || eventType === 'click' || (eventType === 'keyup' && event.keyCode === keyENTER)) {
var list = datalist.id,
inputList = document.querySelector('input[list="' + list + '"]'),
selectValue = select.value;
// input the selects value into the input on a change within the list
if (inputList !== null && typeof( selectValue ) !== 'undefined' && selectValue.length > 0 && selectValue !== message) {
var lastSeperator,
evt;
// in case of type=email and multiple attribute, we need to set up the resulting inputs value differently
inputList.value = (inputList.type === 'email' && inputList.multiple && (lastSeperator = inputList.value.lastIndexOf(',') ) > -1) ?
inputList.value.slice(0, lastSeperator) + ',' + selectValue :
inputList.value = selectValue;
// create and dispatch the input event; divided for IE9 usage
if (typeof(Event) === 'function') {
evt = new Event('input', {
bubbles:true
});
} else {
evt = document.createEvent('Event');
evt.initEvent('input', true, false);
}
inputList.dispatchEvent(evt);
// finally focusing the input, as other browser do this as well
inputList.focus();
// set the visibility to false afterwards, as we're done here
visible = false;
}
}
// toggle the visibility of the datalist select according to previous checks
toggleVisibility(visible, select);
}
};
// toggle the visibility of the datalist select
var toggleVisibility = function(visible, dataListSelect) {
if (visible) {
dataListSelect.removeAttribute('hidden');
} else {
dataListSelect.setAttributeNode(document.createAttribute('hidden'));
}
dataListSelect.setAttribute('aria-hidden', (!visible).toString());
};
// binding the focus event - matching the input[list]s happens in the function afterwards
document.addEventListener('focusin', changesInputList, true);
// Binding the focus event - matching the input[list]s happens in the function afterwards
document.addEventListener('focusin', changesInputList, true);
})();
/*
* datalist-polyfill.js - https://github.com/mfranzke/datalist-polyfill
* Datalist polyfill - https://github.com/mfranzke/datalist-polyfill
* @license Copyright(c) 2017 by Maximilian Franzke
* Supported by Christian, Johannes, @mitchhentges, @mertenhanisch, @ailintom, @Kravimir, @mischah, @hryamzik, @ottoville, @IceCreamYou, @wlekin, @eddr and @beebee1987 - many thanks for that !
*/
!function(){"use strict";if("list"in document.createElement("input")&&document.createElement("datalist")&&window.HTMLDataListElement)return!1;!function(e){e&&e.prototype&&void 0===e.prototype.list&&Object.defineProperty(e.prototype,"list",{get:function(){if("object"==typeof this&&this instanceof e){var t=this.getAttribute("list");return document.getElementById(t)}return null}})}(window.HTMLInputElement),function(e){e&&e.prototype&&void 0===e.prototype.options&&Object.defineProperty(e.prototype,"options",{get:function(){if("object"==typeof this&&this instanceof e)return this.getElementsByTagName("option")}})}(window.HTMLElement);var e=!1,t=13,i=27,n=38,r=40,o=" / ",a=["text","email","number","search","tel","url"],l="polyfilled",s="polyfilling";window.addEventListener("touchstart",function t(){e=!0,window.removeEventListener("touchstart",t)});var u=window.MutationObserver||window.WebKitMutationObserver;if(void 0!==u)var d=new u(function(e){for(var t=!1,i=0;i<e.length;++i)for(var n=0;n<e[i].addedNodes.length;++n)"datalist"===e[i].target.tagName.toLowerCase()&&(t=e[i].target);if(t){var r=document.querySelector('[list="'+t.id+'"]');if(""!==r.value){var o=t;y(c(o,r),o.getElementsByClassName("polyfilling")[0])}}});var p=function(t){var i=t.target,n=i.tagName.toLowerCase(),r=i.list;if(n&&"input"===n&&null!==r){var o=r.getElementsByClassName("polyfilling")[0]||v(i,r);if(void 0!==o){var a=!1;if(27!==t.keyCode&&13!==t.keyCode){var l=i.value,s=38===t.keyCode||40===t.keyCode;if(""!==l||s){c(r,i)&&(a=!0);var u=o.options.length,d=0,p=u-1;if(e)o.selectedIndex=0;else if(-1===o.selectedIndex)switch(t.keyCode){case 38:o.selectedIndex=p;break;case 40:o.selectedIndex=0;break}s&&o.focus()}}y(a,o)}}},c=function(t,i){void 0!==d&&d.disconnect();var n=t.getElementsByClassName("polyfilling")[0]||v(i,t),r=t.querySelectorAll("option:not(:disabled)"),o=i.value,a=document.createDocumentFragment(),l=document.createDocumentFragment();if("email"===i.type&&i.multiple){var s=o.split(","),u=s.length-1;o=s[u].trim()}Array.prototype.slice.call(r).sort(function(e,t){return e.value.localeCompare(t.value)}).forEach(function(e){var t=e.value;if(""!==t&&-1!==t.toLowerCase().indexOf(o.toLowerCase())&&!1===e.disabled){var i=e.getAttribute("label"),n=e.text,r=n.substr(0,t.length+" / ".length),s=t+" / ";n&&!i&&n!==t&&r!==s?e.innerText=t+" / "+n:e.text||(e.innerText=i||t),a.appendChild(e)}else l.appendChild(e)}),n.appendChild(a);var p=n.options.length;return n.size=p>10?10:p,n.multiple=!e&&p<2,(t.getElementsByClassName("ie9_fix")[0]||t).appendChild(l),void 0!==d&&d.observe(t,{childList:!0}),p},f=function(e){var t=e.target,i=t.tagName.toLowerCase(),n=t.list;if(i&&"input"===i&&null!==n){var r=e.type,o=n.getElementsByClassName("polyfilling")[0]||v(t,n),a=o&&o.querySelector("option:not(:disabled)")&&("focusin"===r&&""!==t.value||e.relatedTarget&&e.relatedTarget===o);if(!new RegExp(" polyfilled ").test(" "+t.className+" ")){switch(t.setAttribute("autocomplete","off"),t.setAttribute("role","textbox"),t.setAttribute("aria-haspopup","true"),t.setAttribute("aria-autocomplete","list"),t.setAttribute("aria-owns",t.getAttribute("list")),r){case"focusin":t.addEventListener("keyup",p),t.addEventListener("focusout",f,!0);break;case"blur":t.removeEventListener("keyup",p),t.removeEventListener("focusout",f,!0);break}t.className+=" polyfilled"}y(a,o)}},v=function(t,i){if(a.indexOf(t.type)>-1&&null!==i){var n=i.title,r=t.getClientRects(),o=window.getComputedStyle(t),l=parseFloat(o.getPropertyValue("margin-right")),s=parseFloat(o.getPropertyValue("margin-left")),u=document.createElement("select");if(u.setAttribute("class","polyfilling"),u.style.position="absolute",y(!1,u),u.setAttribute("aria-live","polite"),u.setAttribute("role","listbox"),e||u.setAttribute("aria-multiselectable","false"),"block"===o.getPropertyValue("display")?u.style.marginTop="-"+o.getPropertyValue("margin-bottom"):("rtl"===o.getPropertyValue("direction")?u.style.marginRight="-"+(r[0].width+s)+"px":u.style.marginLeft="-"+(r[0].width+l)+"px",u.style.marginTop=parseInt(r[0].height+(t.offsetTop-i.offsetTop),10)+"px"),u.style.borderRadius=o.getPropertyValue("border-radius"),u.style.minWidth=r[0].width+"px",e){var d=document.createElement("option");d.innerText=n,d.disabled=!0,d.setAttribute("class","message"),u.appendChild(d)}return i.appendChild(u),e?u.addEventListener("change",m):u.addEventListener("click",m),u.addEventListener("blur",m),u.addEventListener("keyup",m),u}},m=function(e){var t=e.target,i=t.tagName.toLowerCase();if(i&&("select"===i||"option"===i)){var n="select"===i?t:t.parentNode,r=n.parentNode,o=r.title,a=e.type,l="keyup"===a&&13!==e.keyCode&&27!==e.keyCode;if("change"===a||"click"===a||"keyup"===a&&13===e.keyCode){var s=r.id,u=document.querySelector('input[list="'+s+'"]'),d=n.value;if(null!==u&&void 0!==d&&d.length>0&&d!==o){var p,c;u.value="email"===u.type&&u.multiple&&(p=u.value.lastIndexOf(","))>-1?u.value.slice(0,p)+","+d:u.value=d,"function"==typeof Event?c=new Event("input",{bubbles:!0}):(c=document.createEvent("Event"),c.initEvent("input",!0,!1)),u.dispatchEvent(c),u.focus(),l=!1}}y(l,n)}},y=function(e,t){e?t.removeAttribute("hidden"):t.setAttributeNode(document.createAttribute("hidden")),t.setAttribute("aria-hidden",(!e).toString())};document.addEventListener("focusin",f,!0)}();
!function(){"use strict";if("list"in document.createElement("input")&&Boolean(document.createElement("datalist")&&window.HTMLDataListElement))return!1;!function(e){e&&e.prototype&&null===e.prototype.list&&Object.defineProperty(e.prototype,"list",{get:function(){return"object"==typeof this&&this instanceof e?document.getElementById(this.getAttribute("list")):null}})}(window.HTMLInputElement),function(e){e&&e.prototype&&null===e.prototype.options&&Object.defineProperty(e.prototype,"options",{get:function(){return"object"==typeof this&&this instanceof e?this.getElementsByTagName("option"):null}})}(window.HTMLElement);var e=!1,t=13,i=27,n=38,o=40,l=" / ",r=["text","email","number","search","tel","url"],a="polyfilled",s="polyfilling";window.addEventListener("touchstart",function t(){e=!0,window.removeEventListener("touchstart",t)});var u=window.MutationObserver||window.WebKitMutationObserver,d;void 0!==u&&(d=new u(function(e){var t=!1;if(e.forEach(function(e){for(var i=0;i<e.addedNodes.length;++i)"datalist"===e.target.tagName.toLowerCase()&&(t=e.target)}),t){var i=document.querySelector('[list="'+t.id+'"]');if(""!==i.value){var n=t;v(c(n,i),n.getElementsByClassName("polyfilling")[0])}}}));var p=function(t){var i=t.target,n=i.tagName.toLowerCase(),o=i.list;if(n&&"input"===n&&null!==o){var l=o.getElementsByClassName("polyfilling")[0]||m(i,o);if(null!==l){var r=!1,a=i.value,s=38===t.keyCode||40===t.keyCode;if(27!==t.keyCode&&13!==t.keyCode&&(""!==a||s)){c(o,i)&&(r=!0);var u=l.options.length,d=0,p=u-1;e?l.selectedIndex=0:-1===l.selectedIndex&&s&&(l.selectedIndex=38===t.keyCode?p:0),s&&l.focus()}v(r,l)}}},c=function(t,i){void 0!==d&&d.disconnect();var n=t.getElementsByClassName("polyfilling")[0]||m(i,t),o=t.querySelectorAll("option:not(:disabled)"),l=i.value,r=document.createDocumentFragment(),a=document.createDocumentFragment();"email"===i.type&&i.multiple&&(l=l.substring(l.lastIndexOf(",")+1)),Array.prototype.slice.call(o).sort(function(e,t){return e.value.localeCompare(t.value)}).forEach(function(e){var t=e.value;if(""!==t&&-1!==t.toLowerCase().indexOf(l.toLowerCase())&&!1===e.disabled){var i=e.getAttribute("label"),n=e.text,o=n.substr(0,t.length+" / ".length),s=t+" / ";n&&!i&&n!==t&&o!==s?e.innerText=t+" / "+n:e.text||(e.innerText=i||t),r.appendChild(e)}else a.appendChild(e)}),n.appendChild(r);var s=n.options.length;return n.size=s>10?10:s,n.multiple=!e&&s<2,(t.getElementsByClassName("ie9_fix")[0]||t).appendChild(a),void 0!==d&&d.observe(t,{childList:!0}),s},f=function(e){var t=e.target,i=t.tagName.toLowerCase(),n=t.list;if(i&&"input"===i&&null!==n){var o=e.type,l=n.getElementsByClassName("polyfilling")[0]||m(t,n),r=l&&l.querySelector("option:not(:disabled)")&&("focusin"===o&&""!==t.value||e.relatedTarget&&e.relatedTarget===l);" "+t.className+" ".indexOf(" polyfilled ")<0&&(t.setAttribute("autocomplete","off"),t.setAttribute("role","textbox"),t.setAttribute("aria-haspopup","true"),t.setAttribute("aria-autocomplete","list"),t.setAttribute("aria-owns",t.getAttribute("list")),"focusin"===o?(t.addEventListener("keyup",p),t.addEventListener("focusout",f,!0)):"blur"===o&&(t.removeEventListener("keyup",p),t.removeEventListener("focusout",f,!0)),t.className+=" polyfilled"),v(r,l)}},m=function(t,i){if(r.indexOf(t.type)>-1&&null!==i){var n=i.title,o=t.getClientRects(),l=window.getComputedStyle(t),a=parseFloat(l.getPropertyValue("margin-right")),s=parseFloat(l.getPropertyValue("margin-left")),u=document.createElement("select");if(u.setAttribute("class","polyfilling"),u.style.position="absolute",v(!1,u),u.setAttribute("aria-live","polite"),u.setAttribute("role","listbox"),e||u.setAttribute("aria-multiselectable","false"),"block"===l.getPropertyValue("display")?u.style.marginTop="-"+l.getPropertyValue("margin-bottom"):("rtl"===l.getPropertyValue("direction")?u.style.marginRight="-"+(o[0].width+s)+"px":u.style.marginLeft="-"+(o[0].width+a)+"px",u.style.marginTop=parseInt(o[0].height+(t.offsetTop-i.offsetTop),10)+"px"),u.style.borderRadius=l.getPropertyValue("border-radius"),u.style.minWidth=o[0].width+"px",e){var d=document.createElement("option");d.innerText=n,d.disabled=!0,d.setAttribute("class","message"),u.appendChild(d)}return i.appendChild(u),e?u.addEventListener("change",y):u.addEventListener("click",y),u.addEventListener("blur",y),u.addEventListener("keyup",y),u}},y=function(e){var t=e.target,i=t.tagName.toLowerCase();if(i&&("select"===i||"option"===i)){var n="select"===i?t:t.parentNode,o=n.parentNode,l=o.title,r=e.type,a="keyup"===r&&13!==e.keyCode&&27!==e.keyCode;if("change"===r||"click"===r||"keyup"===r&&13===e.keyCode){var s=o.id,u=document.querySelector('input[list="'+s+'"]'),d=n.value;if(null!==u&&void 0!==d&&d.length>0&&d!==l){var p,c;u.value="email"===u.type&&u.multiple&&(p=u.value.lastIndexOf(","))>-1?u.value.slice(0,p)+","+d:u.value=d,"function"==typeof Event?c=new Event("input",{bubbles:!0}):(c=document.createEvent("Event"),c.initEvent("input",!0,!1)),u.dispatchEvent(c),u.focus(),a=!1}}v(a,n)}},v=function(e,t){e?t.removeAttribute("hidden"):t.setAttributeNode(document.createAttribute("hidden")),t.setAttribute("aria-hidden",(!e).toString())};document.addEventListener("focusin",f,!0)}();
{
"name": "datalist-polyfill",
"version": "1.15.1",
"version": "1.16.0",
"description": "A lightweight and dependency-free vanilla JavaScript datalist polyfill.",
"main": "datalist-polyfill.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "xo"
},

@@ -17,17 +17,32 @@ "repository": {

"keywords": [
"datalist",
"datalist",
"polyfill",
"javascript",
"html",
"autosuggest",
"autosuggester",
"suggest",
"suggester",
"select"
"autosuggest",
"autosuggester",
"suggest",
"suggester",
"select"
],
"bugs": {
"url": "https://github.com/mfranzke/datalist-polyfill/issues"
"url": "https://github.com/mfranzke/datalist-polyfill/issues"
},
"homepage": "https://github.com/mfranzke/datalist-polyfill",
"dependencies" : {}
"dependencies": {},
"devDependencies": {
"prettier": "^1.13.6",
"xo": "^0.21.1"
},
"xo": {
"envs": [
"browser"
],
"prettier": true,
"esnext": false,
"rules": {
"one-var": 0,
"no-use-before-define": 0
}
}
}
[npm]: https://npmjs.com/package/datalist-polyfill "datalist polyfill – on NPM"
[jsdelivr]: https://www.jsdelivr.com/package/npm/datalist-polyfill "datalist polyfill – on jsDelivr"
[david-dm]: https://david-dm.org/mfranzke/datalist-polyfill "datalist polyfill – on david-dm"
[mit]: https://opensource.org/licenses/mit-license.php
[npm-badge]: https://img.shields.io/npm/v/datalist-polyfill.svg "npm version"
[downl-badge]: https://img.shields.io/npm/dt/datalist-polyfill.svg "Count of total downloads – NPM"
[jsdelivr-badge]: https://data.jsdelivr.com/v1/package/npm/datalist-polyfill/badge "Count of total downloads – jsDelivr"
[dependency-badge]: https://david-dm.org/mfranzke/datalist-polyfill/status.svg "Count of dependencies"
[license-badge]: https://img.shields.io/npm/l/datalist-polyfill.svg "license badge"

@@ -14,6 +6,9 @@

[![datalist-polyfill on Npmjs][npm-badge]][npm]
[![Total downloads ~ Npmjs][downl-badge]][npm]
[![jsDelivr CDN downloads][jsdelivr-badge]][jsdelivr]
[![dependencies Status][dependency-badge]][david-dm]
[![datalist-polyfill on Npmjs](https://img.shields.io/npm/v/datalist-polyfill.svg "npm version")][npm]
[![Total downloads ~ Npmjs](https://img.shields.io/npm/dt/datalist-polyfill.svg "Count of total downloads – NPM")][npm]
[![jsDelivr CDN downloads](https://data.jsdelivr.com/v1/package/npm/datalist-polyfill/badge "Count of total downloads – jsDelivr")](https://www.jsdelivr.com/package/npm/datalist-polyfill "datalist polyfill – on jsDelivr")
[![dependencies Status](https://david-dm.org/mfranzke/datalist-polyfill/status.svg "Count of dependencies")](https://david-dm.org/mfranzke/datalist-polyfill "datalist polyfill – on david-dm")
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d1f98a2d1fd44c41b7ad5c7670d8cdcd)](https://app.codacy.com/app/mfranzke/datalist-polyfill?utm_source=github.com&utm_medium=referral&utm_content=mfranzke/datalist-polyfill&utm_campaign=badger)

@@ -23,7 +18,7 @@ This is a minimal and dependency-free vanilla JavaScript polyfill for the awesome datalist-functionality, that will bring joy and happiness into our lives :-)

* Mainly built for Safari (but supporting IE9 as well), as nearly all of the other browsers [support it quite nicely](https://caniuse.com/#feat=datalist)
* Released under the [![MIT license][license-badge]][mit]
* Released under the [![MIT license](https://img.shields.io/npm/l/datalist-polyfill.svg "license badge")](https://opensource.org/licenses/mit-license.php)
* Made in Germany. And supported by so many great people from all over this planet - see "Credits" accordingly.
## Features
* Lightweight: 5.76 kB of minified JavaScript, around 2.54 kB gzipped
* Lightweight: 5.64 kB of minified JavaScript, around 2.49 kB gzipped
* Fully flexible to change the datalist entries / `<option>`s

@@ -30,0 +25,0 @@ * Supporting:

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc