Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@nrk/core-input
Advanced tools
> `@nrk/core-input` enhances `` fields with keyboard accessible functionality for autocomplete suggestions, search results and smart select box abilities.
@nrk/core-input
enhances<input>
fields with keyboard accessible functionality for autocomplete suggestions, search results and smart select box abilities.
npm install @nrk/core-input --save-exact
import coreInput from '@nrk/core-input' // Vanilla JS
import CoreInput from '@nrk/core-input/jsx' // ...or React/Preact compatible JSX
Typing toggles the hidden attribute on items of type <button>
and <a>
, based on matching textContent. The default filtering behavior can easily be altered through the 'input.select'
and 'input.filter'
events.
<!--demo-->
<input type="text" class="my-input" placeholder="Type "C"...">
<ul hidden>
<li><button>Chrome</button></li>
<li><button>Firefox</button></li>
<li><button>Opera</button></li>
<li><button>Safari</button></li>
<li><button>Microsoft Edge</button></li>
</ul>
<script>
coreInput('.my-input')
</script>
<!--demo-->
<div id="jsx-input"></div>
<script type="text/jsx">
ReactDOM.render(<CoreInput>
<input type='text' placeholder='Type "C"... (JSX)' />
<ul className='my-dropdown'>
<li><button>Chrome</button></li>
<li><button>Firefox</button></li>
<li><button>Opera</button></li>
<li><button>Safari</button></li>
<li><button>Microsoft Edge</button></li>
</ul>
</CoreInput>, document.getElementById('jsx-input'))
</script>
Important: Always use coreInput.escapeHTML(String)
to safely render data from API or user.
Important: Results will always be rendered in the element directly after the <input>
<input type="text" class="my-input"> <!-- Input element must be a <input> -->
<ul hidden> <!-- Can be any tag, but items should be inside <li> -->
<li><button>Item 1</button></li> <!-- Items must be <button> or <a> -->
<li><button value="Suprise!">Item 2</button></li> <!-- Alternative value can be defined -->
<li><a href="https://www.nrk.no/">NRK.no</a></li> <!-- Actual links are recommended when applicable -->
</ul>
import coreInput from '@nrk/core-input'
coreInput(String|Element|Elements, { // Accepts a selector string, NodeList, Element or array of Elements
open: null, // Optional. Defaults to value of aria-expanded. Use true|false to force open state
content: null, // Optional. Set String of HTML content. HTML is used for full flexibility on markup
ajax: null // Optional. Fetch external data, example: "https://search.com?q={{value}}"
})
// Helpers:
coreInput('.my-input', '<li><a href="?q=' + coreInput.escapeHTML(input.value) + '">More results</a></li>') // escape html
coreInput('.my-input', '<li><button>' + coreInput.highlight(item.text, input.value) + '</button></li>') // highlight match
Important: First direct of <CoreToggle>
must be a <input>
and next direct child will be used for results
import CoreInput from '@nrk/core-input/jsx'
// All props are optional, and defaults are shown below
// Props like className, style, etc. will be applied as actual attributes
// <Input> will handle state itself unless you call event.preventDefault() in onFilter or onSelect
<CoreInput open={false} onFilter={(event) => {}} onSelect={(event) => {}} onAjax={(event) => {}} onAjaxBeforeSend={(event) => {}} ajax="https://search.com?q={{value}}">
<input type="text" /> <!-- First element must result in a input-tag. Accepts both elements and components -->
<ul> // Next element will be used for items. Accepts both elements and components
<li><button>Item 1</button></li> <!-- Interactive items must be <button> or <a> -->
<li><button value="Suprise!">Item 2</button></li> // Alternative value can be defined
<li><a href="https://www.nrk.no/">NRK.no</a></li> // Actual links are recommended when applicable
</ul>
</CoreInput>
'input.filter'
is fired before a default filtering (both for VanillaJS and React/Preact components). The input.filter
event is cancelable, meaning you can use event.preventDefault()
to cancel default filtering and respond to users typing yourself. The event also bubbles, and can therefore be detected both from the input element itself, or any parent element (read event delegation):
document.addEventListener('input.filter', (event) => {
event.target // The core-input element triggering input.filter event
event.detail.relatedTarget // The content element controlled by input
})
'input.select'
event is fired when the user clicks/selects a item (both for VanillaJS and React/Preact components). The input.select
event is cancelable, meaning you can use event.preventDefault()
to cancel replacing the input value and handle select-action yourself. The event also bubbles, and can therefore be detected both from the button element itself, or any parent element (read event delegation):
document.addEventListener('input.select', (event) => {
event.target // The core-input element triggering input.select event
event.detail.relatedTarget // The content element controlled by input
event.detail.currentTarget // The item clicked/selected
event.detail.value // The item value
})
The 'input.ajax.beforeSend'
event is fired before sending debounced ajax requests. If you wish to alter the XMLHttpRequest, use event.preventDefault()
and then execute XHR methods on the event.detail
. If not prevented, requests are sent using the GET
method and the header 'X-Requested-With': 'XMLHttpRequest'
. The event bubbles, and can therefore be detected both from the input element itself, or any parent element (read event delegation):
document.addEventListener('input.ajax.beforeSend', (event) => {
event.target // The core-input element triggering input.ajax.beforeSend event
event.detail // The XMLHttpRequest object
})
// Example
document.addEventListener('input.ajax.beforeSend', (event) => {
event.preventDefault() // Stop default behaviour
event.detail.open('POST', 'https://example.com')
event.detail.setRequestHeader('Content-Type', 'application/json')
event.detail.setRequestHeader('my-custom-header', 'my-custom-value')
event.detail.send(JSON.stringify({query: event.target.value}))
})
'input.ajax'
event is fired when the input field receives data from ajax. The event also bubbles, and can therefore be detected both from the input element itself, or any parent element (read event delegation):
document.addEventListener('input.ajax', (event) => {
event.target // The core-input element triggering input.ajax event
event.detail // The ajax request
event.detail.responseText // The response body text
event.detail.responseJSON // The response json. Defaults to false if no valid JSON found
})
All styling in documentation is example only. Both the <button>
and content element receive attributes reflecting the current toggle state:
.my-input {} /* Target input in any state */
.my-input[aria-expanded="true"] {} /* Target only open button */
.my-input[aria-expanded="false"] {} /* Target only closed button */
.my-input-content {} /* Target content element in any state */
.my-input-content:not([hidden]) {} /* Target only open content */
.my-input-content[hidden] {} /* Target only closed content */
.my-input-content :focus {} /* Target focused item */
.my-input-content mark {} /* Target highlighted text */
When using @nrk/core-input
with the ajax: https://search.com?q={{value}}
functionality, make sure to implement both a Searching...
status (while fetching data from the server), and a No hits
status (if server responds with no results). These status indicators are highly recommended, but not provided by default as the context of use will affect the optimal textual formulation. See example implementation →
If you need to alter default headers, request method or post data, use the input.ajax.beforeSend
event →
Ajax requests can be stopped by calling event.preventDefault()
on 'input.filter'
. Remember to always escape html and debounce requests when fetching data from external sources. The http request sent by @nrk/core-input
will have header X-Requested-With: XMLHttpRequest
for easier server side detection and CSRF prevention.
<!--demo-->
<input class="my-input-ajax" placeholder="Country...">
<ul class="my-dropdown" hidden></ul>
<script>
// Initialize
coreInput('.my-input-ajax', {
ajax: 'https://restcountries.eu/rest/v2/name/{{value}}?fields=name'
})
document.addEventListener('input.filter', function (event) {
var input = event.target
var value = input.value.trim()
if (input.className.indexOf('my-input-ajax') === -1) return // Make sure we are on correct input
coreInput(input, value ? '<li><button>Searching for ' + coreInput.highlight(value, value) + '...</button></li>' : '')
})
document.addEventListener('input.ajax', function (event) {
if (event.target.className.indexOf('my-input-ajax') === -1) return // Make sure we are on correct input
var items = event.detail.responseJSON
coreInput(event.target, items.length ? items.slice(0, 10)
.map(function (item) { return coreInput.highlight(item.name, event.target.value) }) // Hightlight items
.map(function (html) { return '<li><button>' + html + '</button></li>' }) // Generate list
.join('') : '<li><button>No results</button></li>')
})
</script>
<!--demo-->
<div id="jsx-input-ajax"></div>
<script type="text/jsx">
class AjaxInput extends React.Component {
constructor (props) {
super(props)
this.onFilter = this.onFilter.bind(this)
this.onAjax = this.onAjax.bind(this)
this.state = { items: [], value: '' }
}
onFilter (event) {
const value = event.target.value
const items = value ? [{name: `Searching for ${value}...`}] : []
this.setState({value, items}) // Store value for highlighting
}
onAjax (event) {
const items = event.detail.responseJSON
this.setState({items: items.length ? items : [{name: 'No results'}]})
}
render () {
return <CoreInput
ajax="https://restcountries.eu/rest/v2/name/{{value}}?fields=name"
onFilter={this.onFilter}
onAjax={this.onAjax}>
<input type='text' placeholder='Country... (JSX)' />
<ul className='my-dropdown'>
{this.state.items.slice(0, 10).map((item, key) =>
<li key={key}>
<button>
<CoreInput.Highlight text={item.name} query={this.state.value} />
</button>
</li>
)}
</ul>
</CoreInput>
}
}
ReactDOM.render(<AjaxInput />, document.getElementById('jsx-input-ajax'))
</script>
Hybrid solution; lazy load items, but let core-input
still handle filtering:
<!--demo-->
<input class="my-input-lazy" placeholder="Country...">
<ul class="my-dropdown" hidden></ul>
<script>
window.getCountries = function (callback) {
var xhr = new XMLHttpRequest()
var url = 'https://restcountries.eu/rest/v2/?fields=name'
xhr.onload = function () { callback(JSON.parse(xhr.responseText)) }
xhr.open('GET', url, true)
xhr.send()
}
document.addEventListener('focus', function (event) {
if (event.target.className.indexOf('my-input-lazy') === -1) return // Make sure we are on correct input
event.target.className = '' // Prevent double execution
window.getCountries(function (items) {
coreInput(event.target, items
.map(function (item) { return '<li><button>' + coreInput.escapeHTML(item.name) + '</button></li>'})
.join(''))
})
}, true)
</script>
<!--demo-->
<div id="jsx-input-lazy"></div>
<script type="text/jsx">
class LazyInput extends React.Component {
constructor (props) {
super(props)
this.onFocus = this.onFocus.bind(this)
this.state = {items: []}
}
onFocus (event) {
this.onFocus = null // Load items only on first interaction
window.getCountries((items) => this.setState({items})) // getCountries defined in JS
}
render () {
return <CoreInput onFocus={this.onFocus}>
<input type='text' placeholder='Country... (JSX)' />
<ul className='my-dropdown'>
{this.state.items.map((item, key) =>
<li key={key}><button>{item.name}</button></li>
)}
</ul>
</CoreInput>
}
}
ReactDOM.render(<LazyInput />, document.getElementById('jsx-input-lazy'))
</script>
Synchronous operation; dynamically populating items based input value:
<!--demo-->
<input class="my-input-dynamic" placeholder="Type your email...">
<ul class="my-dropdown" hidden></ul>
<script>
coreInput('.my-input-dynamic')
document.addEventListener('input.filter', (event) => {
if (event.target.className.indexOf('my-input-dynamic') === -1) return // Make sure we are on correct input
event.preventDefault()
var mails = ['facebook.com', 'gmail.com', 'hotmail.com', 'mac.com', 'mail.com', 'msn.com', 'live.com']
var input = event.target
var value = input.value.trim()
coreInput(input, value ? mails.map(function (mail) {
return '<li><button>' + coreInput.highlight(value.replace(/(@.*|$)/, '@' + mail), value) + '</button><li>'
}).join('') : '')
})
</script>
<!--demo-->
<div id="jsx-input-dynamic"></div>
<script>
class DynamicInput extends React.Component {
constructor (props) {
super(props)
this.onFilter = this.onFilter.bind(this)
this.mails = ['facebook.com', 'gmail.com', 'hotmail.com', 'mac.com', 'mail.com', 'msn.com', 'live.com']
this.state = {items: []}
}
onFilter (event) {
const value = event.target.value.trim()
const items = value ? this.mails.map((mail) => value.replace(/(@.*|$)/, `@${mail}`)) : []
event.preventDefault()
this.setState({value, items})
}
render () {
return <CoreInput onFilter={this.onFilter}>
<input type='text' placeholder='Type your email... (JSX)' />
<ul className='my-dropdown'>
{this.state.items.map((text, key) =>
<li key={key}><button><CoreInput.Highlight text={text} query={this.state.value} /></button></li>
)}
</ul>
</CoreInput>
}
}
ReactDOM.render(<DynamicInput />, document.getElementById('jsx-input-dynamic'))
</script>
<datalist>
instead?Despite having a native <datalist>
element for autocomplete lists, there are several issues regarding browser support, varying accessibility support as well as no ability for custom styling or custom behavior.
Despite well documented examples in the aria 1.1 spesification, "best practice" simply becomes unusable in several screen reader due to implementation differences. core-input
aims to provide a equally good user experience regardless if a screen reader passes events to browser or not (events are often hijacked for quick-navigation). Several techniques and attributes have been thoroughly tested:
aria-activedescendant
/aria-selected
- ignored in Android, lacks indication of list length in JAWSaria-owns
- full list is read for every keystroke in NVDArole="listbox"
- VoiceOver needs aria-selected to falsely announce "0 selected"role="option"
- falsely announces links and buttons as "text"aria-live="assertive"
- fails to correctly inform user if current target is link or buttonrole="combobox"
- skipped in iOS as VoiceOver fails to inform current field is editableTagging and screen readers is a complex matter, requiring more than comma separated values. Currently, tagging is just a part of the wishlist for core-input. If tagging functionality is of interest for your project, please add a +1 to the tagging task, describe your needs in a comment, and you'll be updated about progress.
FAQs
> [Use Core Suggest →](https://github.com/nrkno/core-components/tree/master/packages/core-suggest)
The npm package @nrk/core-input receives a total of 1 weekly downloads. As such, @nrk/core-input popularity was classified as not popular.
We found that @nrk/core-input demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 126 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.