datalist-polyfill
Advanced tools
Comparing version 1.21.2 to 1.22.0
{ | ||
"name": "datalist-polyfill", | ||
"description": "A minimal and dependency-free vanilla JavaScript datalist polyfill. Supports all standard's functionality as well as mimics other browsers behavior.", | ||
"version": "1.21.2", | ||
"version": "1.22.0", | ||
"homepage": "https://github.com/mfranzke/datalist-polyfill", | ||
@@ -6,0 +6,0 @@ "authors": [ |
@@ -10,2 +10,8 @@ # Changelog | ||
## [1.22.0] - 2018-08-27 | ||
### Added | ||
- Substring matching for the suggestions on both the `value` and the `text` values / #GH-36 | ||
## [1.21.2] - 2018-08-11 | ||
@@ -15,7 +21,7 @@ | ||
- Finally integrated the test regarding clicking the selects option elements, as this was actually previously prevented by the other bug fixed in the previous release | ||
- Finally integrated the test regarding clicking the `select`s `option` elements, as this was actually previously prevented by the other bug fixed in the previous release | ||
### Fixed | ||
- Corrected the code style of the readme file again, as this got incorrectly reformatted previously | ||
- Corrected the code style of the README file again, as this got incorrectly reformatted previously | ||
@@ -26,3 +32,3 @@ ## [1.21.1] - 2018-08-09 | ||
- Suggestions aren‘t working onclick any more (#GH-35) | ||
- Suggestions aren‘t working onClick any more (#GH-35) | ||
@@ -29,0 +35,0 @@ ## [1.21.0] - 2018-08-08 |
@@ -16,50 +16,23 @@ /* | ||
// Performance: Set a local variable | ||
var dcmnt = window.document; | ||
// Performance: Set local variables | ||
var dcmnt = window.document, | ||
ua = window.navigator.userAgent, | ||
// Feature detection | ||
datalistSupported = | ||
'list' in dcmnt.createElement('input') && | ||
Boolean(dcmnt.createElement('datalist') && window.HTMLDataListElement), | ||
// IE & EDGE browser detection via UserAgent | ||
// TODO: obviously ugly. But sadly necessary until Microsoft enhances the UX within EDGE (compare to https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9573654/) | ||
// adapted out of https://gist.github.com/gaboratorium/25f08b76eb82b1e7b91b01a0448f8b1d : | ||
isGteIE11orEDGE = Boolean( | ||
ua.indexOf('Trident/') !== -1 || ua.indexOf('Edge/') !== -1 | ||
); | ||
// Feature detection - let's break here, if it's even already supported | ||
if ( | ||
'list' in dcmnt.createElement('input') && | ||
Boolean(dcmnt.createElement('datalist') && window.HTMLDataListElement) | ||
) { | ||
// Let's break here, if it's even already supported ... and not IE11+ or EDGE | ||
if (datalistSupported && !isGteIE11orEDGE) { | ||
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() { | ||
// The list IDL attribute must return the current suggestions source element, if any, or null otherwise. | ||
return typeof this === 'object' && this instanceof constructor | ||
? dcmnt.querySelector('datalist#' + 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 === undefined | ||
) { | ||
Object.defineProperty(constructor.prototype, 'options', { | ||
get: function() { | ||
return typeof this === 'object' && this instanceof constructor | ||
? this.getElementsByTagName('option') | ||
: null; | ||
} | ||
}); | ||
} | ||
})(window.HTMLElement); | ||
// .matches polyfill | ||
// TODO: probably needs enhancement on the to supported browsers | ||
// TODO: probably needs enhancement on the expected to be supported browsers | ||
if (!Element.prototype.matches) { | ||
@@ -82,3 +55,5 @@ Element.prototype.matches = Element.prototype.msMatchesSelector; | ||
classNameInput = 'polyfilled', | ||
classNamePolyfillingSelect = 'polyfilling'; | ||
classNamePolyfillingSelect = 'polyfilling', | ||
// Defining a most likely unique polyfill string | ||
uniquePolyfillString = '###[P0LYFlLLed]###'; | ||
@@ -134,17 +109,31 @@ // Differentiate for touch interactions, adapted by https://medium.com/@david.gilbertson/the-only-way-to-detect-touch-with-javascript-7791a3346685 | ||
datalist = input.list, | ||
// 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(input, datalist); | ||
keyOpen = event.keyCode === keyUP || event.keyCode === keyDOWN; | ||
// Check for whether the events target was an input and still check for an existing instance of the datalist and polyfilling select | ||
if ( | ||
input.tagName.toLowerCase() !== 'input' || | ||
datalist === null || | ||
datalistSelect === undefined | ||
) { | ||
if (input.tagName.toLowerCase() !== 'input' || datalist === null) { | ||
return; | ||
} | ||
// Handling IE11+ & EDGE | ||
if (isGteIE11orEDGE) { | ||
// On keypress check for value | ||
if ( | ||
input.value !== '' && | ||
!keyOpen && | ||
event.keyCode !== keyENTER && | ||
event.keyCode !== keyESC | ||
) { | ||
updateIEOptions(input, datalist); | ||
// TODO: Check whether this update is necessary depending on the options values | ||
input.focus(); | ||
} | ||
return; | ||
} | ||
var visible = false, | ||
keyOpen = event.keyCode === keyUP || event.keyCode === keyDOWN; | ||
// 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(input, datalist); | ||
@@ -155,3 +144,4 @@ // On an ESC or ENTER key press within the input, let's break here and afterwards hide the datalist select, but if the input contains a value or one of the opening keys have been pressed ... | ||
event.keyCode !== keyENTER && | ||
(input.value !== '' || keyOpen) | ||
(input.value !== '' || keyOpen) && | ||
datalistSelect !== undefined | ||
) { | ||
@@ -182,2 +172,148 @@ // ... prepare the options | ||
// On keypress check all options for that as a substring, save the original value as a data-attribute and preset that inputs value (for sorting) for all option values (probably as well enhanced by a token) | ||
var updateIEOptions = function(input, datalist) { | ||
// Loop through the options | ||
Array.prototype.slice.call(datalist.options, 0).forEach(function(option) { | ||
var originalValue = option.dataset.originalvalue || option.value; | ||
// In case of that the original value hasn't been saved as data so far, do that now | ||
if (!option.dataset.originalvalue) { | ||
option.dataset.originalvalue = originalValue; | ||
} | ||
// As we'd manipulate the value in the next step, we'd like to put in that value as either a label or text if none of those exist | ||
if (!option.label && !option.text) { | ||
option.label = originalValue; | ||
} | ||
/* | ||
Check for whether the current option is a valid suggestion and replace its value by | ||
- the current input string, as IE11+ and EDGE don't do substring, but only prefix matching | ||
- followed by a unique string that should prevent any interferance | ||
- and the original string, that is still necessary e.g. for sorting within the suggestions list | ||
As the value is being inserted on users selection, we'll replace that one within the upfollowing inputInputListIE function | ||
*/ | ||
option.value = isValidSuggestion(option, input.value) | ||
? input.value + uniquePolyfillString + originalValue.toLowerCase() | ||
: originalValue; | ||
}); | ||
}; | ||
// Check for the input and probably replace by correct options elements value | ||
var inputInputListIE = function(event) { | ||
var input = event.target, | ||
datalist = input.list; | ||
if ( | ||
!input.matches('input[list]') || | ||
!input.matches('.' + classNameInput) || | ||
!datalist | ||
) { | ||
return; | ||
} | ||
// Query for related option | ||
var option = datalist.querySelector('option[value="' + input.value + '"]'); | ||
if (option && option.dataset.originalvalue) { | ||
input.value = option.dataset.originalvalue; | ||
} | ||
}; | ||
// Check for whether this is a valid suggestion | ||
var isValidSuggestion = function(option, inputValue) { | ||
var optVal = option.value.toLowerCase(), | ||
inptVal = inputValue.toLowerCase(), | ||
label = option.getAttribute('label'), | ||
text = option.text.toLowerCase(); | ||
/* | ||
"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." | ||
"If appropriate, the user agent should use the suggestion's label and value to identify the suggestion to the user." | ||
*/ | ||
return Boolean( | ||
option.disabled === false && | ||
((optVal !== '' && optVal.indexOf(inptVal) !== -1) || | ||
(label && label.toLowerCase().indexOf(inptVal) !== -1) || | ||
(text !== '' && text.indexOf(inptVal) !== -1)) | ||
); | ||
}; | ||
// Focusin and -out events | ||
var changesInputList = function(event) { | ||
// Check for correct element on this event delegation | ||
if (!event.target.matches('input[list]')) { | ||
return; | ||
} | ||
var input = event.target, | ||
datalist = input.list; | ||
// Check for whether the events target was an input and still check for an existing instance of the datalist | ||
if (input.tagName.toLowerCase() !== 'input' || datalist === null) { | ||
return; | ||
} | ||
// Test for whether this input has already been enhanced by the polyfill | ||
if (!input.matches('.' + classNameInput)) { | ||
// We'd like to prevent autocomplete on the input datalist field | ||
input.setAttribute('autocomplete', 'off'); | ||
// WAI ARIA attributes | ||
input.setAttribute('role', 'textbox'); | ||
input.setAttribute('aria-haspopup', 'true'); | ||
input.setAttribute('aria-autocomplete', 'list'); | ||
input.setAttribute('aria-owns', input.getAttribute('list')); | ||
// Bind the keyup event on the related datalists input | ||
if (event.type === 'focusin') { | ||
input.addEventListener('keyup', inputInputList); | ||
input.addEventListener('focusout', changesInputList, true); | ||
if (isGteIE11orEDGE) { | ||
input.addEventListener('input', inputInputListIE); | ||
} | ||
} else if (event.type === 'blur') { | ||
input.removeEventListener('keyup', inputInputList); | ||
input.removeEventListener('focusout', changesInputList, true); | ||
if (isGteIE11orEDGE) { | ||
input.removeEventListener('input', inputInputListIE); | ||
} | ||
} | ||
// Add class for identifying that this input is even already being polyfilled | ||
input.className += ' ' + classNameInput; | ||
} | ||
// Break here for IE11+ & EDGE | ||
if (isGteIE11orEDGE) { | ||
return; | ||
} | ||
var // 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(input, 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)') && | ||
((event.type === 'focusin' && input.value !== '') || | ||
(event.relatedTarget && event.relatedTarget === datalistSelect)); | ||
// Toggle the visibility of the datalist select according to previous checks | ||
toggleVisibility(visible, datalistSelect); | ||
}; | ||
// Binding the focus event - matching the input[list]s happens in the function afterwards | ||
dcmnt.addEventListener('focusin', changesInputList, true); | ||
// Break here for IE11+ & EDGE | ||
if (isGteIE11orEDGE) { | ||
return; | ||
} | ||
// Function for preparing and sorting the options/suggestions | ||
@@ -223,14 +359,10 @@ var prepOptions = function(datalist, input) { | ||
.forEach(function(opt) { | ||
var optionValue = opt.value; | ||
var optionValue = opt.value, | ||
label = opt.getAttribute('label'), | ||
text = opt.text; | ||
// ... 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( | ||
// Put this option into the fragment that is meant to get inserted into the select. Additionally according to the specs ... | ||
// TODO: This might get slightly changed/optimized in a future release | ||
if (isValidSuggestion(opt, inputValue)) { | ||
var textOptionPart = text.substr( | ||
0, | ||
@@ -284,59 +416,2 @@ optionValue.length + textValueSeperator.length | ||
// Focusin and -out events | ||
var changesInputList = function(event) { | ||
// Check for correct element on this event delegation | ||
if (!event.target.matches('input[list]')) { | ||
return; | ||
} | ||
var input = event.target, | ||
datalist = input.list; | ||
// Check for whether the events target was an input and still check for an existing instance of the datalist | ||
if (input.tagName.toLowerCase() !== 'input' || datalist === null) { | ||
return; | ||
} | ||
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(input, 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' && input.value !== '') || | ||
(event.relatedTarget && event.relatedTarget === datalistSelect)); | ||
// Test for whether this input has already been enhanced by the polyfill | ||
if (!input.matches('.' + classNameInput)) { | ||
// We'd like to prevent autocomplete on the input datalist field | ||
input.setAttribute('autocomplete', 'off'); | ||
// WAI ARIA attributes | ||
input.setAttribute('role', 'textbox'); | ||
input.setAttribute('aria-haspopup', 'true'); | ||
input.setAttribute('aria-autocomplete', 'list'); | ||
input.setAttribute('aria-owns', input.getAttribute('list')); | ||
// Bind the keyup event on the related datalists input | ||
if (eventType === 'focusin') { | ||
input.addEventListener('keyup', inputInputList); | ||
input.addEventListener('focusout', changesInputList, true); | ||
} else if (eventType === 'blur') { | ||
input.removeEventListener('keyup', inputInputList); | ||
input.removeEventListener('focusout', changesInputList, true); | ||
} | ||
// Add class for identifying that this input is even already being polyfilled | ||
input.className += ' ' + classNameInput; | ||
} | ||
// Toggle the visibility of the datalist select according to previous checks | ||
toggleVisibility(visible, datalistSelect); | ||
}; | ||
// Define function for setting up the polyfilling select | ||
@@ -542,4 +617,45 @@ var setUpPolyfillingSelect = function(input, datalist) { | ||
// Binding the focus event - matching the input[list]s happens in the function afterwards | ||
dcmnt.addEventListener('focusin', changesInputList, true); | ||
// 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() { | ||
/* | ||
According to the specs ... | ||
"The list IDL attribute must return the current suggestions source element, if any, or null otherwise." | ||
"If there is no list attribute, or if there is no element with that ID, or if the first element with that ID is not a datalist element, then there is no suggestions source element." | ||
*/ | ||
var element = dcmnt.getElementById(this.getAttribute('list')); | ||
return typeof this === 'object' && | ||
this instanceof constructor && | ||
element && | ||
element.matches('datalist') | ||
? element | ||
: 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() { | ||
return typeof this === 'object' && this instanceof constructor | ||
? this.getElementsByTagName('option') | ||
: null; | ||
} | ||
}); | ||
} | ||
})(window.HTMLElement); | ||
})(); |
@@ -6,2 +6,2 @@ /* | ||
*/ | ||
!function(){"use strict";var e=window.document;if("list"in e.createElement("input")&&Boolean(e.createElement("datalist")&&window.HTMLDataListElement))return!1;!function(t){t&&t.prototype&&void 0===t.prototype.list&&Object.defineProperty(t.prototype,"list",{get:function(){return"object"==typeof this&&this instanceof t?e.querySelector("datalist#"+this.getAttribute("list")):null}})}(window.HTMLInputElement),function(e){e&&e.prototype&&void 0===e.prototype.options&&Object.defineProperty(e.prototype,"options",{get:function(){return"object"==typeof this&&this instanceof e?this.getElementsByTagName("option"):null}})}(window.HTMLElement),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector);var t=!1,i=13,n=27,l=38,r=40,a=" / ",o=["text","email","number","search","tel","url"],s="polyfilled",u="polyfilling";window.addEventListener("touchstart",function e(){t=!0,window.removeEventListener("touchstart",e)});var d=window.MutationObserver||window.WebKitMutationObserver,p;void 0!==d&&(p=new d(function(t){var i=!1;if(t.forEach(function(e){for(var t=0;t<e.addedNodes.length;++t)"datalist"===e.target.tagName.toLowerCase()&&(i=e.target)}),i){var n=e.querySelector('input[list="'+i.id+'"]');""!==n.value&&h(y(i,n).length,i.getElementsByClassName("polyfilling")[0])}}));var c=function(e){var i=e.target,n=i.list,l=n.getElementsByClassName("polyfilling")[0]||f(i,n);if("input"===i.tagName.toLowerCase()&&null!==n&&void 0!==l){var r=!1,a=38===e.keyCode||40===e.keyCode;if(27!==e.keyCode&&13!==e.keyCode&&(""!==i.value||a)){y(n,i).length>0&&(r=!0);var o=0,s=l.options.length-1;t?l.selectedIndex=0:a&&"number"!==i.getAttribute("type")&&(l.selectedIndex=38===e.keyCode?s:0,l.focus())}h(r,l)}},y=function(i,n){void 0!==p&&p.disconnect();var l=i.getElementsByClassName("polyfilling")[0]||f(n,i),r=n.value,a=e.createDocumentFragment(),o=e.createDocumentFragment();"email"===n.getAttribute("type")&&null!==n.getAttribute("multiple")&&(r=r.substring(r.lastIndexOf(",")+1)),Array.prototype.slice.call(i.querySelectorAll("option:not(:disabled)")).sort(function(e,t){var i=e.value,l=t.value;return"url"===n.getAttribute("type")&&(i=i.replace(/(^\w+:|^)\/\//,""),l=l.replace(/(^\w+:|^)\/\//,"")),i.localeCompare(l)}).forEach(function(e){var t=e.value;if(""!==t&&-1!==t.toLowerCase().indexOf(r.toLowerCase())&&!1===e.disabled){var i=e.getAttribute("label"),n=e.text,l=n.substr(0,t.length+" / ".length),s=t+" / ";n&&!i&&n!==t&&l!==s?e.innerText=t+" / "+n:e.text||(e.innerText=i||t),a.appendChild(e)}else o.appendChild(e)}),l.appendChild(a);var s=l.options.length;return l.size=s>10?10:s,l.multiple=!t&&s<2,(i.getElementsByClassName("ie9_fix")[0]||i).appendChild(o),void 0!==p&&p.observe(i,{childList:!0}),l.options},v=function(e){if(e.target.matches("input[list]")){var t=e.target,i=t.list;if("input"===t.tagName.toLowerCase()&&null!==i){var n=e.type,l=i.getElementsByClassName("polyfilling")[0]||f(t,i),r=l&&l.querySelector("option:not(:disabled)")&&("focusin"===n&&""!==t.value||e.relatedTarget&&e.relatedTarget===l);t.matches(".polyfilled")||(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"===n?(t.addEventListener("keyup",c),t.addEventListener("focusout",v,!0)):"blur"===n&&(t.removeEventListener("keyup",c),t.removeEventListener("focusout",v,!0)),t.className+=" polyfilled"),h(r,l)}}},f=function(i,n){if(-1!==o.indexOf(i.getAttribute("type"))&&null!==n){var l=i.getClientRects(),r=window.getComputedStyle(i),a=e.createElement("select");if(a.setAttribute("class","polyfilling"),a.style.position="absolute",h(!1,a),a.setAttribute("tabindex","-1"),a.setAttribute("aria-live","polite"),a.setAttribute("role","listbox"),t||a.setAttribute("aria-multiselectable","false"),"block"===r.getPropertyValue("display"))a.style.marginTop="-"+r.getPropertyValue("margin-bottom");else{var s="rtl"===r.getPropertyValue("direction")?"right":"left";a.style.setProperty("margin-"+s,"-"+(l[0].width+parseFloat(r.getPropertyValue("margin-"+s)))+"px"),a.style.marginTop=parseInt(l[0].height+(i.offsetTop-n.offsetTop),10)+"px"}if(a.style.borderRadius=r.getPropertyValue("border-radius"),a.style.minWidth=l[0].width+"px",t){var u=e.createElement("option");u.innerText=n.title,u.disabled=!0,u.setAttribute("class","message"),a.appendChild(u)}return n.appendChild(a),t?a.addEventListener("change",m):a.addEventListener("click",m),a.addEventListener("blur",m),a.addEventListener("keydown",m),a.addEventListener("keypress",g),a}},g=function(t){var i=t.target,n=i.parentNode,l=e.querySelector('input[list="'+n.id+'"]');"select"===i.tagName.toLowerCase()&&null!==l&&(!t.key||"Backspace"!==t.key&&1!==t.key.length||(l.focus(),"Backspace"===t.key?(l.value=l.value.substr(0,l.value.length-1),b(l)):l.value+=t.key,y(n,l)))},m=function(t){var i=t.currentTarget,n=i.parentNode,l=e.querySelector('input[list="'+n.id+'"]');if("select"===i.tagName.toLowerCase()&&null!==l){var r=t.type,a="keydown"===r&&13!==t.keyCode&&27!==t.keyCode;if(("change"===r||"click"===r||"keydown"===r&&(13===t.keyCode||"Tab"===t.key))&&i.value.length>0&&i.value!==n.title){var o;l.value="email"===l.getAttribute("type")&&null!==l.getAttribute("multiple")&&(o=l.value.lastIndexOf(","))>-1?l.value.slice(0,o)+","+i.value:l.value=i.value,b(l),"Tab"!==t.key&&l.focus(),a=!1}else"keydown"===r&&27===t.keyCode&&l.focus();h(a,i)}},b=function(t){var i;"function"==typeof Event?i=new Event("input",{bubbles:!0}):(i=e.createEvent("Event"),i.initEvent("input",!0,!1)),t.dispatchEvent(i)},h=function(t,i){t?i.removeAttribute("hidden"):i.setAttributeNode(e.createAttribute("hidden")),i.setAttribute("aria-hidden",(!t).toString())};e.addEventListener("focusin",v,!0)}(); | ||
!function(){"use strict";var e=window.document,t=window.navigator.userAgent,i="list"in e.createElement("input")&&Boolean(e.createElement("datalist")&&window.HTMLDataListElement),a=Boolean(-1!==t.indexOf("Trident/")||-1!==t.indexOf("Edge/"));if(i&&!a)return!1;Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector);var n=!1,l=13,r=27,o=38,s=40,u=" / ",d=["text","email","number","search","tel","url"],p="polyfilled",c="polyfilling",v="###[P0LYFlLLed]###";window.addEventListener("touchstart",function e(){n=!0,window.removeEventListener("touchstart",e)});var y=window.MutationObserver||window.WebKitMutationObserver,f;void 0!==y&&(f=new y(function(t){var i=!1;if(t.forEach(function(e){for(var t=0;t<e.addedNodes.length;++t)"datalist"===e.target.tagName.toLowerCase()&&(i=e.target)}),i){var a=e.querySelector('input[list="'+i.id+'"]');""!==a.value&&x(E(i,a).length,i.getElementsByClassName("polyfilling")[0])}}));var g=function(e){var t=e.target,i=t.list,l=38===e.keyCode||40===e.keyCode;if("input"===t.tagName.toLowerCase()&&null!==i){if(a)return void(""===t.value||l||13===e.keyCode||27===e.keyCode||(m(t,i),t.focus()));var r=!1,o=i.getElementsByClassName("polyfilling")[0]||C(t,i);if(27!==e.keyCode&&13!==e.keyCode&&(""!==t.value||l)&&void 0!==o){E(i,t).length>0&&(r=!0);var s=0,u=o.options.length-1;n?o.selectedIndex=0:l&&"number"!==t.getAttribute("type")&&(o.selectedIndex=38===e.keyCode?u:0,o.focus())}x(r,o)}},m=function(e,t){Array.prototype.slice.call(t.options,0).forEach(function(t){var i=t.dataset.originalvalue||t.value;t.dataset.originalvalue||(t.dataset.originalvalue=i),t.label||t.text||(t.label=i),t.value=h(t,e.value)?e.value+"###[P0LYFlLLed]###"+i.toLowerCase():i})},b=function(e){var t=e.target,i=t.list;if(t.matches("input[list]")&&t.matches(".polyfilled")&&i){var a=i.querySelector('option[value="'+t.value+'"]');a&&a.dataset.originalvalue&&(t.value=a.dataset.originalvalue)}},h=function(e,t){var i=e.value.toLowerCase(),a=t.toLowerCase(),n=e.getAttribute("label"),l=e.text.toLowerCase();return Boolean(!1===e.disabled&&(""!==i&&-1!==i.indexOf(a)||n&&-1!==n.toLowerCase().indexOf(a)||""!==l&&-1!==l.indexOf(a)))},w=function(e){if(e.target.matches("input[list]")){var t=e.target,i=t.list;if("input"===t.tagName.toLowerCase()&&null!==i&&(t.matches(".polyfilled")||(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"===e.type?(t.addEventListener("keyup",g),t.addEventListener("focusout",w,!0),a&&t.addEventListener("input",b)):"blur"===e.type&&(t.removeEventListener("keyup",g),t.removeEventListener("focusout",w,!0),a&&t.removeEventListener("input",b)),t.className+=" polyfilled"),!a)){var n=i.getElementsByClassName("polyfilling")[0]||C(t,i),l=n&&n.querySelector("option:not(:disabled)")&&("focusin"===e.type&&""!==t.value||e.relatedTarget&&e.relatedTarget===n);x(l,n)}}};if(e.addEventListener("focusin",w,!0),!a){var E=function(t,i){void 0!==f&&f.disconnect();var a=t.getElementsByClassName("polyfilling")[0]||C(i,t),l=i.value,r=e.createDocumentFragment(),o=e.createDocumentFragment();"email"===i.getAttribute("type")&&null!==i.getAttribute("multiple")&&(l=l.substring(l.lastIndexOf(",")+1)),Array.prototype.slice.call(t.querySelectorAll("option:not(:disabled)")).sort(function(e,t){var a=e.value,n=t.value;return"url"===i.getAttribute("type")&&(a=a.replace(/(^\w+:|^)\/\//,""),n=n.replace(/(^\w+:|^)\/\//,"")),a.localeCompare(n)}).forEach(function(e){var t=e.value,i=e.getAttribute("label"),a=e.text;if(h(e,l)){var n=a.substr(0,t.length+" / ".length),s=t+" / ";a&&!i&&a!==t&&n!==s?e.innerText=t+" / "+a:e.text||(e.innerText=i||t),r.appendChild(e)}else o.appendChild(e)}),a.appendChild(r);var s=a.options.length;return a.size=s>10?10:s,a.multiple=!n&&s<2,(t.getElementsByClassName("ie9_fix")[0]||t).appendChild(o),void 0!==f&&f.observe(t,{childList:!0}),a.options},C=function(t,i){if(-1!==d.indexOf(t.getAttribute("type"))&&null!==i){var a=t.getClientRects(),l=window.getComputedStyle(t),r=e.createElement("select");if(r.setAttribute("class","polyfilling"),r.style.position="absolute",x(!1,r),r.setAttribute("tabindex","-1"),r.setAttribute("aria-live","polite"),r.setAttribute("role","listbox"),n||r.setAttribute("aria-multiselectable","false"),"block"===l.getPropertyValue("display"))r.style.marginTop="-"+l.getPropertyValue("margin-bottom");else{var o="rtl"===l.getPropertyValue("direction")?"right":"left";r.style.setProperty("margin-"+o,"-"+(a[0].width+parseFloat(l.getPropertyValue("margin-"+o)))+"px"),r.style.marginTop=parseInt(a[0].height+(t.offsetTop-i.offsetTop),10)+"px"}if(r.style.borderRadius=l.getPropertyValue("border-radius"),r.style.minWidth=a[0].width+"px",n){var s=e.createElement("option");s.innerText=i.title,s.disabled=!0,s.setAttribute("class","message"),r.appendChild(s)}return i.appendChild(r),n?r.addEventListener("change",k):r.addEventListener("click",k),r.addEventListener("blur",k),r.addEventListener("keydown",k),r.addEventListener("keypress",L),r}},L=function(t){var i=t.target,a=i.parentNode,n=e.querySelector('input[list="'+a.id+'"]');"select"===i.tagName.toLowerCase()&&null!==n&&(!t.key||"Backspace"!==t.key&&1!==t.key.length||(n.focus(),"Backspace"===t.key?(n.value=n.value.substr(0,n.value.length-1),A(n)):n.value+=t.key,E(a,n)))},k=function(t){var i=t.currentTarget,a=i.parentNode,n=e.querySelector('input[list="'+a.id+'"]');if("select"===i.tagName.toLowerCase()&&null!==n){var l=t.type,r="keydown"===l&&13!==t.keyCode&&27!==t.keyCode;if(("change"===l||"click"===l||"keydown"===l&&(13===t.keyCode||"Tab"===t.key))&&i.value.length>0&&i.value!==a.title){var o;n.value="email"===n.getAttribute("type")&&null!==n.getAttribute("multiple")&&(o=n.value.lastIndexOf(","))>-1?n.value.slice(0,o)+","+i.value:n.value=i.value,A(n),"Tab"!==t.key&&n.focus(),r=!1}else"keydown"===l&&27===t.keyCode&&n.focus();x(r,i)}},A=function(t){var i;"function"==typeof Event?i=new Event("input",{bubbles:!0}):(i=e.createEvent("Event"),i.initEvent("input",!0,!1)),t.dispatchEvent(i)},x=function(t,i){t?i.removeAttribute("hidden"):i.setAttributeNode(e.createAttribute("hidden")),i.setAttribute("aria-hidden",(!t).toString())};!function(t){t&&t.prototype&&void 0===t.prototype.list&&Object.defineProperty(t.prototype,"list",{get:function(){var i=e.getElementById(this.getAttribute("list"));return"object"==typeof this&&this instanceof t&&i&&i.matches("datalist")?i:null}})}(window.HTMLInputElement),function(e){e&&e.prototype&&void 0===e.prototype.options&&Object.defineProperty(e.prototype,"options",{get:function(){return"object"==typeof this&&this instanceof e?this.getElementsByTagName("option"):null}})}(window.HTMLElement)}}(); |
{ | ||
"name": "datalist-polyfill", | ||
"version": "1.21.2", | ||
"version": "1.22.0", | ||
"description": "A minimal and dependency-free vanilla JavaScript datalist polyfill. Supports all standard's functionality as well as mimics other browsers behavior.", | ||
@@ -5,0 +5,0 @@ "main": "datalist-polyfill.js", |
@@ -18,3 +18,3 @@ [npm]: https://npmjs.com/package/datalist-polyfill 'datalist polyfill – on NPM' | ||
- Supports all standard's functionality as well as mimics other browsers behavior. | ||
- 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) | ||
- Mitigating the [different levels of support](https://caniuse.com/#feat=datalist) both by Safari and IE9+ as well as EDGE | ||
- Released under the MIT license | ||
@@ -25,3 +25,3 @@ - Made in Germany. And supported by so many great people from all over this planet - see "Credits" accordingly. | ||
- Lightweight: 6.23 kB of minified JavaScript, around 2.66 kB gzipped | ||
- Lightweight: 5.6 kB of minified JavaScript, around 2.2 kB gzipped | ||
- Fully flexible to change the datalist entries / `<option>`s | ||
@@ -39,2 +39,3 @@ - Supporting: | ||
- on `input[type=url]` omitting the scheme part and performing intelligent matching on the domain name | ||
- substring matching on both the `value` and the `text` values | ||
- Emits "input" event when item in the `datalist` is selected | ||
@@ -138,2 +139,3 @@ - Enables core keyboard controls such as the | ||
- Windows 7 SP1, Internet Explorer 9.0.8112.16421 | ||
- Windows 8.1, Internet Explorer 11.0.9600.19101 | ||
@@ -148,2 +150,38 @@ ### Big Thanks | ||
## Prospects & functionality overview | ||
The following problems are mainly reported and [listed on caniuse](https://caniuse.com/#feat=datalist) as well as due to issues flagged on Github. | ||
<table> | ||
<tr> | ||
<th>Problem</th> | ||
<th>iOS</th> | ||
<th>Safari</th> | ||
<th>IE9</th> | ||
<th>IE11+</th> | ||
<th>EDGE</th> | ||
<th>Firefox</th> | ||
<th>Chrome</th> | ||
<th>Chrome WebView</th> | ||
</tr> | ||
<tr> | ||
<th align="left">Basic functionality</th> | ||
<td colspan="3" align="center">✔ <i>Polyfill</i></td> | ||
<td colspan="4" align="center">✔</td> | ||
<td align="center"><a href="https://github.com/mfranzke/datalist-polyfill/issues/33">#GH-33</a></td> | ||
</tr> | ||
<tr> | ||
<th align="left"><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=773041">long lists of items are unscrollable resulting in unselectable options</a></th> | ||
<td colspan="6"> </td> | ||
<td>fixed with v.70</td> | ||
<td> </td> | ||
</tr> | ||
<tr> | ||
<th align="left"><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9573654/">No substring matching for the suggestions</a></th> | ||
<td colspan="3">✔</td> | ||
<td colspan="2" align="center">✔ by <a href="https://github.com/mfranzke/datalist-polyfill/issues/36">#GH-36</a></td> | ||
<td colspan="3">✔</td> | ||
</tr> | ||
</table> | ||
## Outro | ||
@@ -150,0 +188,0 @@ |
@@ -40,3 +40,3 @@ exports.config = { | ||
exclude: [ | ||
// 'path/to/excluded/files' | ||
// 'path/to/excluded/files' | ||
], | ||
@@ -75,3 +75,3 @@ // | ||
version: '11.0' | ||
}/*, | ||
} /*, | ||
// TODO: Fix the test cases regarding older versions of safari/selenium | ||
@@ -78,0 +78,0 @@ { |
@@ -6,2 +6,5 @@ var field = { fieldId: 'animal', initialValue: 'Ca', expectedAmount: 2, wrongValue: 'Cc' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'animal2', initialValue: 'Ca', expectedAmount: 2, wrongValue: 'Cc' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -30,2 +30,5 @@ var field = { fieldId: 'email', initialValue: '@r', expectedAmount: 4, wrongValue: 'doe1' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -32,0 +35,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'number', initialValue: '19', expectedAmount: 28, wrongValue: '99' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'options', initialValue: ' 1', expectedAmount: 4, wrongValue: 'options' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'search', initialValue: 'ma', expectedAmount: 2, wrongValue: 'google' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'tel', initialValue: '-3', expectedAmount: 6, wrongValue: '911' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -6,2 +6,5 @@ var field = { fieldId: 'url', initialValue: '.u', expectedAmount: 3, wrongValue: 'ftp:' }; | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -8,0 +11,0 @@ |
@@ -0,1 +1,4 @@ | ||
/* global browser $ $$ */ | ||
/* eslint-env mocha */ | ||
var expect = require('chai').expect; | ||
@@ -2,0 +5,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
135539
32
2068
189