UNPKG

manuel

Version:

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

633 lines (551 loc) 14.8 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.manuel = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ // @ts-check /* eslint-disable fp/no-mutating-methods */ /** * @typedef { (x: VNode ) => VNode } VNodeTransform * @param { VNodeTransform } f */ function $root(f){ /** * @param { VNode } o */ function action(o){ var r = f(o) return typeof r !== 'undefined' ? r : o } return action } /** * @param { VNodeTransform } f */ function $ul(f){ /** * @param { VNode } div */ function action(div){ var r = f(div.children[1]) if( typeof r !== 'undefined' ){ // eslint-disable-next-line fp/no-mutation div.children[1] = r } return div } return action } /** * @param { ( o: VNode[]) => VNode[] } f */ function $li( f ){ return $ul(function(ul){ var r = f(ul.children) if( typeof r !== 'undefined' ){ // eslint-disable-next-line fp/no-mutation ul.children = r } return ul }) } /** * @param { VNodeTransform } f */ function $input(f){ /** * @param { VNode } div */ function action(div){ var r = f(div.children[0]) if( typeof r !== 'undefined' ){ // eslint-disable-next-line fp/no-mutation div.children[0] = r } return div } return action } /* * The following utilities are all adapted from * https://github.com/LeaVerou/awesomplete */ /** * @type { (input: string, text: string ) => boolean } */ var contains = function contains(input, text){ return input.trim().length ? RegExp(regExpEscape(input.trim()), "i").test(text) : true } /** * @type { (s: string ) => string } */ var regExpEscape = function regExpEscape(s) { return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); } /** * @type { {(a: string, b: string ) : number} } */ var sortByLength = function sortByLength(a, b) { if (a.length !== b.length) { return a.length - b.length; } return a < b ? -1 : 1; }; /** @type { (p: { [k: string]: any }, b: [string, any] ) => { [k:string] : any } } */ var assignPair = function assignPair(p, pair){ // eslint-disable-next-line fp/no-mutation p[ pair[0] ] = pair[1] return p } var keyboard = { submit: function submit( /** @type {number} */code ,/** @type {string} */ highlighted ) { return code == 13 && highlighted ? [highlighted] : [] } ,dismiss: function dismiss( /** @type {number} */ code ){ return code == 27 ? [true] : [] } ,navigate: function navigate( /** @type {boolean} */ showingDrawer ,/** @type {string} */ highlighted ,/** @type {string[]} */ renderedList ,/** @type {number} */ 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 [] } } } /** @typedef {any} VNode @typedef { (tagName: string, attrs: object, children: VNode[] ) => VNode } HyperscriptConstructor @typedef { ( key: any ) => any } FrameworkGet @typedef { ( key: any, value: any ) => any } FrameworkSet @typedef {{ get: FrameworkGet set: FrameworkSet hyperscript: HyperscriptConstructor }} Framework @typedef {{ list: any input: any chosen: any open: any highlighted: any }} Model @typedef { Event & { currentTarget: { value: String }} } InputEvent @typedef {{ minChars: number maxItems: number sort: { (input: string, text: string ): number } filter: { (value: string, s: string): boolean } filteredList: string[] eventNames: { [k:string]: string } showingDrawer: boolean choose: { (x:string) : void } clickItem: { (x:string) : void } PATTERN_INPUT: RegExp | null mark: { (x:string) : VNode } highlight: { (x:string) : VNode } oninput: { (e: InputEvent ): void } onfocus: { (e: FocusEvent ): void } close: { (): void } onblur: { (e: FocusEvent ): void } renderInput: { (config:Overrides) : VNode } itemClassNames: { (x:string, config: Overrides) : string } renderItem: { (x:string, config: Overrides): VNode } renderItems: { (config: Overrides): VNode } classNames: { () : string } renderRoot: { (config: Overrides): VNode } keyboardSubmit: { (code: number, highlighted: string ): string[] } keyboardDismiss: { (code: number ): boolean[] } keyboardNavigate: { ( showingDrawer: boolean , highlighted: string , renderedList: string[] , code: number ) : string[] } onkeydown: { (e: KeyboardEvent ): void } }} Overrides @param {Framework} framework */ function BaseAutocomplete(framework){ var h = framework.hyperscript var get = framework.get var set = framework.set /** * * @param {Model} model * @param { Partial<Overrides> } nullableOverrides */ function Autocomplete(model, nullableOverrides){ var overrides = nullableOverrides || {} var list = model.list var input = model.input var chosen = model.chosen var open = model.open /** @type { () => string[] } */ var getList = function(){ return get(list) } /** @type { () => string } */ var getInput = function(){ return get(input) } /** @type { () => string } */ var getChosen = function(){ return get(chosen) } /** @type { () => boolean } */ var getOpen = function(){ return get(open) } /** @type { () => string } */ var getHighlighted = function(){ return get(highlighted) } var highlighted = model.highlighted var value = getInput() 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 : getList() .filter(function (s){ return filter(value, s) }) .sort( sort ) .slice(0, maxItems) if( getHighlighted() != null && filteredList.indexOf( get( highlighted ) ) == -1 ){ set(highlighted, null) } if( getChosen() != null && getChosen() != value ){ set(chosen, null) } /** @type {Overrides} */ var config = { filteredList: filteredList ,minChars: minChars ,maxItems: maxItems ,sort: sort ,filter: filter ,eventNames: typeof overrides.eventNames != 'undefined' ? overrides.eventNames : { oninput: 'oninput' , onfocus: 'onfocus' , onblur: 'onblur' , onkeydown: 'onkeydown' , onmousedown: 'onmousedown' } ,showingDrawer: typeof overrides.showingDrawer != 'undefined' ? overrides.showingDrawer : getOpen() && value.length >= minChars && filteredList.length > 0 ,choose: typeof overrides.choose != 'undefined' ? overrides.choose : function choose(x){ if( getInput() != x ){ set(input, x) } if( getChosen() != x ){ set(chosen, 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 ){ /** @type {string[] | null} */ var matches = config.PATTERN_INPUT != null ? x.match( config.PATTERN_INPUT ) : null /** @type {{ buffer: string, output: string[] }} */ var initial = { buffer: x ,output: [] } 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) ) ] ) } }, initial ) : { output: [x], buffer: '' } return processed.output.concat( processed.buffer || [] ) } ,oninput: typeof overrides.oninput != 'undefined' ? overrides.oninput : function oninput(e){ var v = e.currentTarget.value if( getInput() != v ){ set(input, v) } if( !getOpen() ){ set(open, true) } } ,onfocus: typeof overrides.onfocus != 'undefined' ? overrides.onfocus : function onfocus(){ if( !getOpen() ){ set(open, true) } } ,close: typeof overrides.close != 'undefined' ? overrides.close : function close(){ //eslint-disable-next-line fp/no-mutation if( getOpen() ){ 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] , [config.eventNames.oninput, config.oninput] , [config.eventNames.onfocus, config.onfocus] , [config.eventNames.onblur, config.onblur] ] .reduce(assignPair, {}) , [] ) } ,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) ] , [ config.eventNames.onmousedown, function( /** @type {Event} */ e ){ config.clickItem(x) e.stopPropagation() }] ] .reduce(assignPair, {}) , 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'] : [] ,getList().length > 0 ? ['loaded'] : [] ) .join(' ') } ,renderRoot: typeof overrides.renderRoot != 'undefined' ? overrides.renderRoot : function renderRoot(config){ return h('div' ,[[ 'className', config.classNames() ] , [config.eventNames.onkeydown, config.onkeydown] ] .reduce(assignPair, {}) ,[ 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 = e.shiftKey ? [] : 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 ) new_chosen.length + dismiss.length + new_highlighted.length > 0 && e.preventDefault() } } return config.renderRoot(config) } return Autocomplete } module.exports = BaseAutocomplete // eslint-disable-next-line fp/no-mutation BaseAutocomplete.queries = { listItems: $li , list: $ul , root: $root , input: $input } },{}]},{},[1])(1) });