UNPKG

manuel

Version:

A super customizable VDOM autocomplete with *production ready* defaults.

382 lines (342 loc) 8.73 kB
/* eslint-disable fp/no-mutating-methods */ /* * The following utilities are all adapted from * https://github.com/LeaVerou/awesomplete */ // String -> String -> Boolean function contains(input, text) { return input.trim().length ? RegExp(regExpEscape(input.trim()), "i").test(text) : true } // String -> String function regExpEscape(s) { return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); } // a -> b -> Number function sortByLength(a, b) { if (a.length !== b.length) { return a.length - b.length; } return a < b? -1 : 1; }; var keyboard = { submit: function submit(code, highlighted){ return code == 13 && highlighted ? [highlighted] : [] } ,dismiss: function dismiss(code){ return code == 27 ? [true] : [] } ,navigate: function navigate( showingDrawer , highlighted , renderedList , code ){ var KEY_UP = 38 var KEY_DOWN = 40 var i = renderedList.indexOf(highlighted) var NO_MATCH = i == -1 var LOWER_BOUND = 0 var UPPER_BOUND = renderedList.length -1 var MATCH = i >= -1 var NEXT = i+1 var PREV = i-1 if( showingDrawer && (code == KEY_UP || code == KEY_DOWN) ){ if( code == KEY_UP && (NO_MATCH || i == LOWER_BOUND) ){ return [renderedList[UPPER_BOUND]] } else if(code == KEY_UP && MATCH) { return [renderedList[PREV]] } else if ( code == KEY_DOWN && ( NO_MATCH || i == UPPER_BOUND ) ) { return [renderedList[LOWER_BOUND]] } else { // ( code == KEY_DOWN && MATCH ) return [renderedList[NEXT]] } } else { return [] } } } function BaseAutocomplete(framework){ var h = framework.hyperscript var get = framework.get var set = framework.set function Autocomplete(model, nullableOverrides){ var overrides = nullableOverrides || {} var list = model.list var input = model.input var chosen = model.chosen var open = model.open var highlighted = model.highlighted var value = get(input) var minChars = typeof overrides.minChars != 'undefined' ? overrides.minChars : 2 var maxItems = typeof overrides.maxItems != 'undefined' ? overrides.maxItems : 10 var sort = typeof overrides.sort != 'undefined' ? overrides.sort : sortByLength var filter = typeof overrides.filter != 'undefined' ? overrides.filter : contains var filteredList = typeof overrides.filteredList != 'undefined' ? overrides.filteredList : get( list ) .filter(function (s){ return filter(value, s) }) .sort( sort ) .slice(0, maxItems) if( filteredList.indexOf( get( highlighted ) ) == -1 ){ set(highlighted, null) } var config = { filteredList: filteredList ,showingDrawer: typeof overrides.showingDrawer != 'undefined' ? overrides.showingDrawer : get(open) && value.length >= minChars && filteredList.length > 0 ,choose: typeof overrides.choose != 'undefined' ? overrides.choose : function choose(x){ set(chosen, x) set(input, x) config.close() } ,clickItem: typeof overrides.clickItem != 'undefined' ? overrides.clickItem : function clickItem(x){ return config.choose(x) } ,PATTERN_INPUT: typeof overrides.PATTERN_INPUT != 'undefined' ? overrides.PATTERN_INPUT : value ? new RegExp(value, 'gi') : null ,mark: typeof overrides.mark != 'undefined' ? overrides.mark : function(x){ return h('mark', x) } ,highlight: typeof overrides.highlight != 'undefined' ? overrides.highlight : function highlight( x ){ var matches = config.PATTERN_INPUT != null ? x.match( config.PATTERN_INPUT ) : null var processed = matches != null ? matches .reduce(function(p, n){ var i = p.buffer.indexOf(n) return { buffer: p.buffer.slice(i+n.length) ,output: p.output.concat( i === 0 ? [] : p.buffer.slice(0, i) ,[ config.mark( p.buffer.slice(i, i+n.length) ) ] ) } }, { buffer: x ,output: [] }) : { output: x, buffer: '' } return processed.output.concat( processed.buffer || [] ) } ,oninput: typeof overrides.oninput != 'undefined' ? overrides.oninput : function oninput(e){ var v = e.currentTarget.value set(input, v) if( !get(open) ){ set(open, true) } if( get(chosen) != v ){ set(chosen, null) } } ,onfocus: typeof overrides.onfocus != 'undefined' ? overrides.onfocus : function onfocus(){ if( !get(open) ){ set(open, true) } } ,close: typeof overrides.close != 'undefined' ? overrides.close : function close(){ //eslint-disable-next-line fp/no-mutation if( get(open) ){ set(open, false) } } ,onblur: typeof overrides.onblur != 'undefined' ? overrides.onblur : function onblur(){ config.close() } ,renderInput: typeof overrides.renderInput != 'undefined' ? overrides.renderInput : function renderInput(){ return h('input' ,{ value: value , oninput: config.oninput , onfocus: config.onfocus , onblur: config.onblur } ) } ,itemClassNames: typeof overrides.itemClassNames != 'undefined' ? overrides.itemClassNames : function itemClassNames(x){ return x == get(highlighted) ? 'highlight' : '' } ,renderItem: typeof overrides.renderItem != 'undefined' ? overrides.renderItem : function renderItem(x, config){ return h( 'li' , { className: config.itemClassNames(x, config) , onmousedown: function(e){ config.clickItem(x) e.stopPropagation() } } , config.highlight(x) ) } ,renderItems: typeof overrides.renderItems != 'undefined' ? overrides.renderItems : function renderItems(config){ return h( 'ul' , config.filteredList.map( function filteredList$map(x){ return config.renderItem(x, config) } ) ) } ,classNames: typeof overrides.classNames != 'undefined' ? overrides.classNames : function classNames(){ return ['manuel-complete'] .concat( config.showingDrawer ? ['open'] : [] ,value.length > 0 ? ['not-empty'] : [] ,get(list).length > 0 ? ['loaded'] : [] ) .join(' ') } ,renderRoot: typeof overrides.renderRoot != 'undefined' ? overrides.renderRoot : function renderRoot(config){ return h('div' ,{ className: config.classNames() , onkeydown: config.onkeydown } ,config.renderInput(config) ,config.renderItems(config) ) } ,keyboardSubmit: typeof overrides.keyboardSubmit != 'undefined' ? overrides.keyboardSubmit : keyboard.submit ,keyboardDismiss: typeof overrides.keyboardDismiss != 'undefined' ? overrides.keyboardDismiss : keyboard.dismiss ,keyboardNavigate: typeof overrides.keyboardNavigate != 'undefined' ? overrides.keyboardNavigate : keyboard.navigate ,onkeydown: typeof overrides.onkeydown != 'undefined' ? overrides.onkeydown : function onkeydown(e){ var new_chosen = config.keyboardSubmit( e.keyCode , get(highlighted) ) var dismiss = config.keyboardDismiss( e.keyCode ) var new_highlighted = config.keyboardNavigate( config.showingDrawer , get(highlighted) , config.filteredList , e.keyCode ) new_chosen.map( config.choose ) new_highlighted.map( function new_highlighted$map(v){ return set(highlighted, v) } ) dismiss.map( config.close ) ;[] .concat( new_chosen ,dismiss ,new_highlighted ) .slice(0,1) .map( function e$preventDefault(){ e.preventDefault() return null; } ) } } return config.renderRoot(config) } return Autocomplete } module.exports = BaseAutocomplete