From f60e3fb16cb3de873cd15b8d02d09a35ad976325 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 15 Mar 2024 10:01:46 +0100 Subject: [PATCH] chore: coverage dom module, refactoring focus method --- .github/workflows/build.yml | 2 +- cypress-api/index.js | 17 -- demo/css/kompletr.demo.min.css | 1 + {cypress-api => demo/files}/data.json | 0 demo/index.html | 109 +++++++++++++ demo/js/kompletr.min.js | 2 + demo/js/kompletr.min.js.map | 1 + package.json | 12 +- src/index.html | 6 +- src/js/dom.js | 29 ++-- src/js/kompletr.js | 3 +- .../{kompleter.scss => kompletr.demo.scss} | 0 src/sass/kompletr.scss | 154 ++++++++++++++++++ test/dom.spec.js | 42 ++++- test/kompletr.spec.js | 3 +- webpack.config.dev.js | 35 ---- 16 files changed, 326 insertions(+), 90 deletions(-) delete mode 100644 cypress-api/index.js create mode 100644 demo/css/kompletr.demo.min.css rename {cypress-api => demo/files}/data.json (100%) create mode 100644 demo/index.html create mode 100644 demo/js/kompletr.min.js create mode 100644 demo/js/kompletr.min.js.map rename src/sass/{kompleter.scss => kompletr.demo.scss} (100%) mode change 100755 => 100644 create mode 100755 src/sass/kompletr.scss delete mode 100644 webpack.config.dev.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b25bac..d54fbfa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,6 +80,6 @@ jobs: name: build-files path: dist - name: Run E2E tests - run: npm run ci:dev && npm run cypress:run + run: npm run ci:dev & npm run cypress:run env: CI: true \ No newline at end of file diff --git a/cypress-api/index.js b/cypress-api/index.js deleted file mode 100644 index ad9a254..0000000 --- a/cypress-api/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import http from 'http'; -import * as records from './data.json' assert { type: "json" }; - -/** - * A small server for E2E tests - */ -http - .createServer((req, res) => { - const qs = new URLSearchParams(req.url.split('?')[1]) - const response = qs.get('q') ? records.default.filter(record => record["Name"].toLowerCase().lastIndexOf(qs.get('q').toLowerCase()) === 0) : records.default; - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.end(JSON.stringify(response)); - }) - .listen(3000, (err) => { - console.log('server is listening on port 3000'); - }); \ No newline at end of file diff --git a/demo/css/kompletr.demo.min.css b/demo/css/kompletr.demo.min.css new file mode 100644 index 0000000..a22cb79 --- /dev/null +++ b/demo/css/kompletr.demo.min.css @@ -0,0 +1 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans");@import url("https://fonts.googleapis.com/css?family=Thasadith");html{font-size:14px}.github-corner{position:fixed;right:0;top:0;border-bottom:0;text-decoration:none;z-index:1}.github-corner svg{height:80px;width:80px;color:#fff;fill:#333;fill:var(--theme-color, #333)}body{display:flex;flex-direction:column;margin:0;min-height:100vh;font-family:"Open Sans",sans-serif;background:#020024;background:linear-gradient(90deg, #020024 0%, #85ffbd 50%, #fffb7d 100%)}hgroup{margin:0 auto 40px auto;text-align:center}h1{margin:3rem auto 0 auto;font-family:"Thasadith",sans-serif;font-size:7rem}a{color:#333}footer{margin-top:auto;color:#333;text-align:center}.kompletr.form--search{width:30%;position:relative;margin:0 auto}@media (max-width: 480px){.kompletr.form--search{width:90%}}.kompletr .input--search,.kompletr .item--result{font-family:"Open Sans",sans-serif;font-size:100%}.kompletr .input--search{display:block;box-sizing:border-box;margin:0 auto;padding:15px 10px;width:100%;min-width:240px;max-width:600px;height:auto;font-size:1.2rem;line-height:1.5;border:none}.kompletr .input--search:focus{border:none;outline:none}.kompletr .form--search__result{position:absolute;margin:0;width:100%}.kompletr .item--result{box-sizing:border-box;width:100%;padding:15px;display:flex;flex-wrap:wrap;justify-content:space-between;border-left:none;border-right:none}.kompletr .item--result:last-child{border-bottom:none}.kompletr .item--result:hover,.kompletr .item--result.focus{cursor:pointer;-webkit-transition:all 0.2s ease-in-out ease-in-out;transition:all 0.2s ease-in-out ease-in-out}.kompletr .item--result .item--data{flex:50%}.kompletr .item--result .item--data:nth-child(even){text-align:right}.kompletr .item--result .item--data:nth-child(0){font-weight:600}.kompletr .item--result .item--data:nth-child(n+2){font-weight:normal}.kompletr.light .input--search{color:#9e9e9e;background:#fff}.kompletr.light ::placeholder{color:silver}.kompletr.light .item--result{color:#333;border-bottom:1px dashed #dfdfdf;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);background-color:rgba(255,255,255,0.75)}.kompletr.light .item--result:hover,.kompletr.light .item--result.focus{backdrop-filter:blur(26px) saturate(120%);-webkit-backdrop-filter:blur(26px) saturate(120%);background-color:rgba(255,255,255,0.5)}.kompletr.light .item--result:hover .item--data:nth-child(n+2),.kompletr.light .item--result.focus .item--data:nth-child(n+2){color:#333}.kompletr.light .item--result .item--data:nth-child(0){color:#333}.kompletr.light .item--result .item--data:nth-child(n+2){color:#9e9e9e}.kompletr.dark .input--search{color:#fff;background:#333}.kompletr.dark ::placeholder{color:silver}.kompletr.dark .item--result{color:#fff;border-bottom:1px dashed #9e9e9e;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);background-color:rgba(51,51,51,0.75)}.kompletr.dark .item--result:hover,.kompletr.dark .item--result.focus{backdrop-filter:blur(26px) saturate(120%);-webkit-backdrop-filter:blur(26px) saturate(120%);background-color:rgba(51,51,51,0.5)}.kompletr.dark .item--result:hover .item--data:nth-child(n+2),.kompletr.dark .item--result.focus .item--data:nth-child(n+2){color:#fff}.kompletr.dark .item--result .item--data:nth-child(0){color:#fff}.kompletr.dark .item--result .item--data:nth-child(n+2){color:#9e9e9e} diff --git a/cypress-api/data.json b/demo/files/data.json similarity index 100% rename from cypress-api/data.json rename to demo/files/data.json diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..e956de9 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,109 @@ + + + + + Vanilla autocomplete module - Kømpletr.js + + + + + + + + + + + + + + + + + +
+

Documentation

+ + + + +
+ +
+

Kømpletr

+ 10kb of vanilla lightweight to add highly featured and eco friendly autocomplete on your pages. +
+ + + + + + + + diff --git a/demo/js/kompletr.min.js b/demo/js/kompletr.min.js new file mode 100644 index 0000000..f94b7ce --- /dev/null +++ b/demo/js/kompletr.min.js @@ -0,0 +1,2 @@ +var t={d:function(e,s){for(var r in s)t.o(s,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:s[r]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}},e={};t.d(e,{Z:function(){return y}});const s=Object.freeze({fadeIn:"fadeIn",slideDown:"slideDown"}),r=Object.freeze({error:"kompletr.error",domDone:"kompletr.dom.done",dataDone:"kompletr.data.done",selectDone:"kompletr.select.done"}),i=Object.freeze({cache:"cache",callback:"callback",local:"local"}),o=Object.freeze({prefix:"prefix",expression:"expression"}),a=Object.freeze({light:"light",dark:"dark"});class n{constructor(){}static fadeIn(t,e,s=500){t.style.opacity=0,t.style.display=e||"block",function e(){let s=parseFloat(t.style.opacity);(s+=.1)>1||(t.style.opacity=s,requestAnimationFrame(e))}()}static fadeOut(t,e=500){t.style.opacity=1,function e(){(t.style.opacity-=.1)<0?t.style.display="none":requestAnimationFrame(e)}()}static slideUp(t,e=500){t.style.transitionProperty="height, margin, padding",t.style.transitionDuration=e+"ms",t.style.boxSizing="border-box",t.style.height=t.offsetHeight+"px",t.offsetHeight,t.style.overflow="hidden",t.style.height=0,t.style.paddingTop=0,t.style.paddingBottom=0,t.style.marginTop=0,t.style.marginBottom=0,window.setTimeout((()=>{t.style.display="none",t.style.removeProperty("height"),t.style.removeProperty("padding-top"),t.style.removeProperty("padding-bottom"),t.style.removeProperty("margin-top"),t.style.removeProperty("margin-bottom"),t.style.removeProperty("overflow"),t.style.removeProperty("transition-duration"),t.style.removeProperty("transition-property")}),e)}static slideDown(t,e=500){t.style.removeProperty("display"),console.log(t);let s=window.getComputedStyle(t).display;"none"===s&&(s="block"),t.style.display=s;let r=t.offsetHeight;t.style.overflow="hidden",t.style.height=0,t.style.paddingTop=0,t.style.paddingBottom=0,t.style.marginTop=0,t.style.marginBottom=0,t.offsetHeight,t.style.boxSizing="border-box",t.style.transitionProperty="height, margin, padding",t.style.transitionDuration=e+"ms",t.style.height=r+"px",t.style.removeProperty("padding-top"),t.style.removeProperty("padding-bottom"),t.style.removeProperty("margin-top"),t.style.removeProperty("margin-bottom"),window.setTimeout((()=>{t.style.removeProperty("height"),t.style.removeProperty("overflow"),t.style.removeProperty("transition-duration"),t.style.removeProperty("transition-property")}),e)}static animateBack(t,e=s.fadeIn,r=500){return n[{fadeIn:"fadeOut",slideDown:"slideUp"}[e]](t,r)}}class l{broadcaster=null;cache=null;callbacks={};configuration=null;dom=null;props=null;constructor({configuration:t,properties:e,dom:s,cache:i,broadcaster:o,onKeyup:a,onSelect:n,onError:l}){try{this.configuration=t,this.broadcaster=o,this.props=e,this.dom=s,this.cache=i,this.broadcaster.subscribe(r.error,this.error),this.broadcaster.subscribe(r.dataDone,this.showResults),this.broadcaster.subscribe(r.domDone,this.bindResults),this.broadcaster.subscribe(r.selectDone,this.closeTheShop),this.broadcaster.listen(this.dom.input,"keyup",this.suggest),this.broadcaster.listen(this.dom.body,"click",this.closeTheShop),(a||n||l)&&(this.callbacks=Object.assign(this.callbacks,{onKeyup:a,onSelect:n,onError:l}))}catch(t){o.trigger(r.error,t)}}closeTheShop=t=>{if(t.srcElement===this.dom.input)return!0;n.animateBack(this.dom.result,this.configuration.animationType,this.configuration.animationDuration),this.resetPointer()};resetPointer=()=>{this.props.pointer=-1};error=t=>{console.error(`[kompletr] An error has occured -> ${t.stack}`),n.fadeIn(this.dom.result),this.callbacks.onError&&this.callbacks.onError(t)};showResults=async({from:t,data:e})=>{this.props.data=e,e=this.props.data.map(((t,e)=>({idx:e,data:t}))),this.callbacks.onKeyup||(e=e.filter((t=>{const e="string"==typeof t.data?t.data:t.data[this.configuration.propToMapAsValue];return"prefix"===this.configuration.filterOn?0===e.toLowerCase().lastIndexOf(this.dom.input.value.toLowerCase(),0):-1!==e.toLowerCase().lastIndexOf(this.dom.input.value.toLowerCase())}))),this.cache&&t!==i.cache&&this.cache.set({string:this.dom.input.value,data:e}),this.dom.buildResults(e.slice(0,this.configuration.maxResults),this.configuration.fieldsToDisplay)};bindResults=()=>{if(n[this.configuration.animationType](this.dom.result,this.configuration.animationDuration),this.dom.result?.children?.length)for(let t=0;t{this.broadcaster.listen(this.dom.result.children[t],"click",(()=>{this.dom.focused=this.dom.result.children[t],this.select(this.dom.focused.id)}))})(t)};suggest=t=>{if(this.dom.input.value.length{try{this.cache&&await this.cache.isValid(t)?this.cache.get(t,(t=>{this.broadcaster.trigger(r.dataDone,{from:i.cache,data:t})})):this.callbacks.onKeyup?this.callbacks.onKeyup(t,(t=>{this.broadcaster.trigger(r.dataDone,{from:i.callback,data:t})})):this.broadcaster.trigger(r.dataDone,{from:i.local,data:this.props.data})}catch(t){this.broadcaster.trigger(r.error,t)}};navigate=t=>(38==t||40==t)&&!(this.props.pointer<-1||this.props.pointer>this.dom.result.children.length-1)&&(38===t&&this.props.pointer>=-1?this.props.pointer--:40===t&&this.props.pointer{this.dom.input.value="object"==typeof this.props.data[t]?this.props.data[t][this.configuration.propToMapAsValue]:this.props.data[t],this.callbacks.onSelect(this.props.data[t]),this.broadcaster.trigger(r.selectDone)}}class h{_animationType=s.fadeIn;_animationDuration=500;_multiple=!1;_theme=a.light;_fieldsToDisplay=[];_maxResults=10;_startQueriyngFromChar=2;_propToMapAsValue="";_filterOn=o.prefix;_cache=0;get animationType(){return this._animationType}set animationType(t){const e=Object.keys(s);if(!e.includes(t))throw new Error(`animation.type should be one of ${e.toString()}`);this._animationType=t}get animationDuration(){return this._animationDuration}set animationDuration(t){if(isNaN(parseInt(t,10)))throw new Error("animation.duration should be an integer");this._animationDuration=t}get multiple(){return this._multiple}set multiple(t){this._multiple=t}get theme(){return this._theme}set theme(t){const e=Object.keys(a);if(!e.includes(t))throw new Error(`theme should be one of ${e.toString()}, ${t} given`);this._theme=t}get fieldsToDisplay(){return this._fieldsToDisplay}set fieldsToDisplay(t){this._fieldsToDisplay=t}get maxResults(){return this._maxResults}set maxResults(t){this._maxResults=t}get startQueriyngFromChar(){return this._startQueriyngFromChar}set startQueriyngFromChar(t){this._startQueriyngFromChar=t}get propToMapAsValue(){return this._propToMapAsValue}set propToMapAsValue(t){this._propToMapAsValue=t}get filterOn(){return this._filterOn}set filterOn(t){const e=Object.keys(o);if(!e.includes(t))throw new Error(`filterOn should be one of ${e.toString()}, ${t} given`);this._filterOn=t}get cache(){return this._cache}set cache(t){if(isNaN(parseInt(t,10)))throw new Error("cache should be an integer");this._cache=t}constructor(t){if(void 0!==t){if("object"!=typeof t)throw new Error("options should be an object");this._theme=t?.theme||this._theme,this._animationType=t?.animationType||this._animationType,this._animationDuration=t?.animationDuration||this._animationDuration,this._multiple=t?.multiple||this._multiple,this._fieldsToDisplay=t?.fieldsToDisplay||this._fieldsToDisplay,this._maxResults=t?.maxResults||this._maxResults,this._startQueriyngFromChar=t?.startQueriyngFromChar||this._startQueriyngFromChar,this._propToMapAsValue=t?.propToMapAsValue||this._propToMapAsValue,this._filterOn=t?.filterOn||this._filterOn,this._cache=t?.cache||this._cache}}}class c{_name=null;_duration=null;_braodcaster=null;constructor(t,e=0,s="kompletr.cache"){if(!window.caches)return!1;this._broadcaster=t,this._name=s,this._duration=e}get(t,e){window.caches.open(this._name).then((s=>{s.match(t).then((async t=>{e(await t.json())}))})).catch((t=>{this._broadcaster.trigger(r.error,t)}))}set({string:t,data:e}){window.caches.open(this._name).then((s=>{const r=new Headers;r.set("Content-Type","application/json"),r.set("Cache-Control",`max-age=${this._duration}`),s.put(`/${t}`,new Response(JSON.stringify(e),{headers:r}))})).catch((t=>{this._broadcaster.trigger(r.error,t)}))}async isValid(t){try{const e=await window.caches.open(this._name);return!!await e.match(`/${t}`)}catch(t){this._broadcaster.trigger(r.error,t)}}}class u{subscribers=[];constructor(){}subscribe(t,e){if(!Object.values(r).includes(t))throw new Error(`Event should be one of ${Object.keys(r)}: ${t} given.`);this.subscribers.push({type:t,handler:e})}listen(t,e,s){t.addEventListener(e,s)}trigger(t,e={}){if(!Object.values(r).includes(t))throw new Error(`Event should be one of ${Object.keys(r)}: ${t} given.`);this.subscribers.filter((e=>e.type===t)).forEach((t=>t.handler(e)))}}class p{_data=null;get data(){return this._data}set data(t){if(!Array.isArray(t))throw new Error(`data must be an array (${t.toString()} given)`);this._data=t}_pointer=null;get pointer(){return this._pointer}set pointer(t){if(isNaN(parseInt(t,10)))throw new Error(`pointer must be an integer (${t.toString()} given)`);this._pointer=t}_previousValue=null;get previousValue(){return this._previousValue}set previousValue(t){this._previousValue=t}constructor(t=[]){this._data=t}}class d{_body=null;get body(){return this._body}set body(t){this._body=t}_input=null;get input(){return this._input}set input(t){if(input instanceof HTMLInputElement==0)throw new Error(`input should be an HTMLInputElement instance: ${input} given.`);this._input=t}_focused=null;get focused(){return this._focused}set focused(t){this._focused=t}_result=null;get result(){return this._result}set result(t){this._result=t}_broadcaster=null;constructor(t,e,s={theme:"light"}){this._body=document.getElementsByTagName("body")[0],this._input=t instanceof HTMLInputElement?t:document.getElementById(t),this._input.setAttribute("class",`${this._input.getAttribute("class")} input--search`),this._result=this.build("div",[{id:"kpl-result"},{class:"form--search__result"}]),this._input.parentElement.setAttribute("class",`${this._input.parentElement.getAttribute("class")} kompletr ${s.theme}`),this._input.parentElement.appendChild(this._result),this._broadcaster=e}build(t,e=[]){const s=document.createElement(t);return e.forEach((t=>{s.setAttribute(Object.keys(t)[0],Object.values(t)[0])})),s}focus(t,e){if(!["add","remove"].includes(e))throw new Error('action should be one of ["add", "remove]: '+e+" given.");switch(e){case"add":this.focused=this.result.children[t],this.result.children[t].className+=" focus";break;case"remove":this.focused=null,Array.from(this.result.children).forEach((t=>{(t=>{t.className="item--result"})(t)}))}}buildResults(t,e){let s="";s=t&&t.length?t.reduce(((t,s)=>{switch(t+=`
`,typeof s.data){case"string":t+=`${s.data}`;break;case"object":let r=Array.isArray(e)&&e.length?e:Object.keys(s.data);for(let e=0;e${s.data[r[e]]}`}return t+"
"}),""):'
Not found
',this.result.innerHTML=s,this._broadcaster.trigger(r.domDone)}}const m=function({data:t,options:e,onKeyup:s,onSelect:r,onError:i}){const o=new u,a=new h(e),n=new p(t),m=new d(this,o,a),y=a.cache?new c(o,a.cache):null;new l({configuration:a,properties:n,dom:m,cache:y,broadcaster:o,onKeyup:s,onSelect:r,onError:i})};window.HTMLInputElement.prototype.kompletr=m;var y=m,g=e.Z;export{g as default}; +//# sourceMappingURL=kompletr.min.js.map \ No newline at end of file diff --git a/demo/js/kompletr.min.js.map b/demo/js/kompletr.min.js.map new file mode 100644 index 0000000..0f54687 --- /dev/null +++ b/demo/js/kompletr.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"kompletr.min.js","mappings":"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","sources":["webpack://kompletr/webpack/bootstrap","webpack://kompletr/webpack/runtime/define property getters","webpack://kompletr/webpack/runtime/hasOwnProperty shorthand","webpack://kompletr/./src/js/enums.js","webpack://kompletr/./src/js/animation.js","webpack://kompletr/./src/js/kompletr.js","webpack://kompletr/./src/js/configuration.js","webpack://kompletr/./src/js/cache.js","webpack://kompletr/./src/js/broadcaster.js","webpack://kompletr/./src/js/properties.js","webpack://kompletr/./src/js/dom.js","webpack://kompletr/./src/js/index.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","/**\n * Enum representing different animation types.\n * \n * @enum {string}\n * @readonly\n */\nconst animation = Object.freeze({\n fadeIn: 'fadeIn',\n slideDown: 'slideDown',\n});\n\n\n/**\n * Enum representing different custom events.\n * \n * @enum {string}\n * @readonly\n */\nconst event = Object.freeze({\n error: 'kompletr.error',\n domDone: 'kompletr.dom.done',\n dataDone: 'kompletr.data.done',\n selectDone: 'kompletr.select.done'\n});\n\n/**\n * Enum representing the origin of a value.\n * \n * @enum {string}\n * @readonly\n */\nconst origin = Object.freeze({\n cache: 'cache',\n callback: 'callback',\n local: 'local',\n});\n\n/**\n * Enum representing the search expression options.\n * \n * @enum {string}\n * @readonly\n */\nconst searchExpression = Object.freeze({\n prefix: 'prefix',\n expression: 'expression',\n});\n\n/**\n * Enum representing the theme options.\n *\n * @enum {string}\n * @readonly\n */\nconst theme = Object.freeze({\n light: 'light',\n dark: 'dark',\n});\n\nexport { animation, event, origin, searchExpression, theme }","import { animation } from \"./enums.js\";\n\n/**\n * Represents an Animation class that provides various animation effects.\n */\nexport class Animation {\n constructor() {}\n\n /**\n * Apply a fadeIn animation effect to the target HTML element.\n * \n * @param {HTMLElement} element - The target HTML element.\n * @param {String} display - The CSS3 display property value.\n * @param {Number} duration - The duration of the animation in milliseconds.\n * \n * @returns {Void}\n * \n * @todo Manage duration\n */\n static fadeIn(element, display, duration = 500) {\n element.style.opacity = 0;\n element.style.display = display || 'block';\n (function fade(){\n let value = parseFloat(element.style.opacity);\n if (!((value += .1) > 1)) {\n element.style.opacity = value;\n requestAnimationFrame(fade);\n }\n })();\n };\n\n /**\n * Apply a fadeOut animation effect to the target HTML element.\n * \n * @param {HTMLElement} element - The target HTML element.\n * @param {Number} duration - The duration of the animation in milliseconds.\n * \n * @returns {Void}\n * \n * @todo Manage duration\n */\n static fadeOut(element, duration = 500) {\n element.style.opacity = 1;\n (function fade() {\n if ((element.style.opacity -= .1) < 0) {\n element.style.display = 'none';\n } else {\n requestAnimationFrame(fade);\n }\n })();\n };\n\n /**\n * Apply a slideUp animation effect to the target HTML element.\n * \n * @param {HTMLElement} element - The target HTML element.\n * @param {Number} duration - The duration of the animation in milliseconds.\n * \n * @returns {Void}\n */\n static slideUp(element, duration = 500) {\n element.style.transitionProperty = 'height, margin, padding';\n element.style.transitionDuration = duration + 'ms';\n element.style.boxSizing = 'border-box';\n element.style.height = element.offsetHeight + 'px';\n element.offsetHeight;\n element.style.overflow = 'hidden';\n element.style.height = 0;\n element.style.paddingTop = 0;\n element.style.paddingBottom = 0;\n element.style.marginTop = 0;\n element.style.marginBottom = 0;\n window.setTimeout( () => {\n element.style.display = 'none';\n element.style.removeProperty('height');\n element.style.removeProperty('padding-top');\n element.style.removeProperty('padding-bottom');\n element.style.removeProperty('margin-top');\n element.style.removeProperty('margin-bottom');\n element.style.removeProperty('overflow');\n element.style.removeProperty('transition-duration');\n element.style.removeProperty('transition-property');\n }, duration);\n };\n\n /**\n * Apply a slideDown animation effect to the target HTML element.\n * \n * @param {HTMLElement} element - The target HTML element.\n * @param {Number} duration - The duration of the animation in milliseconds.\n * \n * @returns {Void}\n */\n static slideDown(element, duration = 500) {\n element.style.removeProperty('display');\n console.log(element)\n let display = window.getComputedStyle(element).display;\n if (display === 'none') display = 'block';\n element.style.display = display;\n let height = element.offsetHeight;\n element.style.overflow = 'hidden';\n element.style.height = 0;\n element.style.paddingTop = 0;\n element.style.paddingBottom = 0;\n element.style.marginTop = 0;\n element.style.marginBottom = 0;\n element.offsetHeight;\n element.style.boxSizing = 'border-box';\n element.style.transitionProperty = \"height, margin, padding\";\n element.style.transitionDuration = duration + 'ms';\n element.style.height = height + 'px';\n element.style.removeProperty('padding-top');\n element.style.removeProperty('padding-bottom');\n element.style.removeProperty('margin-top');\n element.style.removeProperty('margin-bottom');\n window.setTimeout( () => {\n element.style.removeProperty('height');\n element.style.removeProperty('overflow');\n element.style.removeProperty('transition-duration');\n element.style.removeProperty('transition-property');\n },duration);\n };\n\n /**\n * Apply the opposite animation effect to a given element.\n * \n * @param {HTMLElement} element - The element to animate.\n * @param {string} [type=animation.fadeIn] - The animation to apply. By default, it's 'fadeIn'.\n * @param {number} [duration=500] - The duration of the animation in milliseconds. By default, it's 500.\n * \n * @return {Object} Returns the result of the Animation function with the opposite animation, the element, and the duration as parameters.\n */\n static animateBack(element, type = animation.fadeIn , duration = 500) {\n const animations = {\n fadeIn: 'fadeOut',\n slideDown: 'slideUp'\n };\n return Animation[animations[type]](element, duration);\n }\n};","\nimport { Animation } from './animation.js';\nimport { event, origin } from './enums.js';\n\n/**\n * @summary Kømpletr.js is a library providing features dedicated to autocomplete fields.\n * \n * @author Steve Lebleu \n * \n * @see https://github.com/steve-lebleu/kompletr\n */\nexport default class Kompletr {\n broadcaster = null;\n\n /**\n * \n */\n cache = null;\n\n /**\n * \n */\n callbacks = {};\n\n /**\n * \n */\n configuration = null;\n\n /**\n * \n */\n dom = null;\n\n /**\n * \n */\n props = null;\n\n constructor({ configuration, properties, dom, cache, broadcaster, onKeyup, onSelect, onError }) {\n try {\n this.configuration = configuration;\n this.broadcaster = broadcaster;\n this.props = properties;\n this.dom = dom;\n this.cache = cache;\n\n this.broadcaster.subscribe(event.error, this.error);\n this.broadcaster.subscribe(event.dataDone, this.showResults);\n this.broadcaster.subscribe(event.domDone, this.bindResults);\n this.broadcaster.subscribe(event.selectDone, this.closeTheShop);\n\n this.broadcaster.listen(this.dom.input, 'keyup', this.suggest);\n this.broadcaster.listen(this.dom.body, 'click', this.closeTheShop); // TODO: validate this because it can be called many times if many kompletr instances\n\n if(onKeyup || onSelect || onError) {\n this.callbacks = Object.assign(this.callbacks, { onKeyup, onSelect, onError });\n }\n } catch(e) {\n broadcaster.trigger(event.error, e);\n }\n }\n\n closeTheShop = (e) => {\n if (e.srcElement === this.dom.input) {\n return true;\n }\n Animation.animateBack(this.dom.result, this.configuration.animationType, this.configuration.animationDuration);\n this.resetPointer();\n }\n\n resetPointer = () => {\n this.props.pointer = -1;\n }\n\n error = (e) => {\n console.error(`[kompletr] An error has occured -> ${e.stack}`);\n Animation.fadeIn(this.dom.result);\n this.callbacks.onError && this.callbacks.onError(e);\n }\n\n /**\n * @description CustomEvent 'this.request.done' listener\n * \n * @todo Check something else to determine if we filter or not -> currently just the presence of onKeyup callback\n */\n showResults = async ({ from, data }) => {\n this.props.data = data;\n\n data = this.props.data.map((record, idx) => ({ idx, data: record }) ); // TODO: Check if we can avoid this step\n\n if (!this.callbacks.onKeyup) {\n data = data.filter((record) => {\n const value = typeof record.data === 'string' ? record.data : record.data[this.configuration.propToMapAsValue];\n if (this.configuration.filterOn === 'prefix') {\n return value.toLowerCase().lastIndexOf(this.dom.input.value.toLowerCase(), 0) === 0;\n }\n return value.toLowerCase().lastIndexOf(this.dom.input.value.toLowerCase()) !== -1;\n });\n }\n\n if (this.cache && from !== origin.cache) {\n this.cache.set({ string: this.dom.input.value, data });\n }\n\n this.dom.buildResults(data.slice(0, this.configuration.maxResults), this.configuration.fieldsToDisplay);\n }\n\n /**\n * @description CustomEvent 'kompletr.dom.done' listener\n */\n bindResults = () => { \n Animation[this.configuration.animationType](this.dom.result, this.configuration.animationDuration); // TODO this is not really bindResult\n if(this.dom.result?.children?.length) {\n for(let i = 0; i < this.dom.result.children.length; i++) {\n ((i) => {\n return this.broadcaster.listen(this.dom.result.children[i], 'click', () => {\n this.dom.focused = this.dom.result.children[i];\n this.select(this.dom.focused.id);\n });\n })(i)\n }\n }\n }\n\n /**\n * @description 'input.keyup' listener\n */\n suggest = (e) => {\n if (this.dom.input.value.length < this.configuration.startQueriyngFromChar) {\n return;\n }\n \n const keyCode = e.keyCode;\n\n switch (keyCode) {\n case 13: // Enter\n this.select(this.dom.focused.id);\n break;\n case 38: // Up\n case 40: // Down\n this.navigate(keyCode);\n break;\n default:\n if (this.dom.input.value !== this.props.previousValue) {\n this.hydrate(this.dom.input.value);\n }\n this.resetPointer();\n break\n }\n }\n\n /**\n * @description Manage the data hydration according to the current setup (cache, request or local data)\n * \n * @param {String} value Current input value\n * \n * @emits CustomEvent 'this.request.done' { from, data }\n * @emits CustomEvent 'this.error' { error }\n * \n * @returns {Void}\n * \n * @todo options.data could returns Promise, and same for the onKeyup callback\n */\n hydrate = async (value) => {\n try {\n if (this.cache && await this.cache.isValid(value)) {\n this.cache.get(value, (data) => {\n this.broadcaster.trigger(event.dataDone, { from: origin.cache, data: data }); \n });\n } else if (this.callbacks.onKeyup) {\n this.callbacks.onKeyup(value, (data) => {\n this.broadcaster.trigger(event.dataDone, { from: origin.callback, data: data });\n });\n } else {\n this.broadcaster.trigger(event.dataDone, { from: origin.local, data: this.props.data });\n }\n } catch(e) {\n this.broadcaster.trigger(event.error, e);\n }\n }\n\n /**\n * @description Apply visual navigation into the suggestions set\n * \n * @param {Number} keyCode The current keyCode value\n * \n * @returns {Void}\n */\n navigate = (keyCode) => {\n if (keyCode != 38 && keyCode != 40) {\n return false;\n }\n\n if(this.props.pointer < -1 || this.props.pointer > this.dom.result.children.length - 1) {\n return false;\n }\n\n if (keyCode === 38 && this.props.pointer >= -1) {\n this.props.pointer--;\n } else if (keyCode === 40 && this.props.pointer < this.dom.result.children.length - 1) {\n this.props.pointer++;\n } \n\n this.dom.focus(this.props.pointer, 'remove');\n this.dom.focus(this.props.pointer, 'add');\n }\n\n /**\n * @description Select a suggested item as user choice\n * \n * @param {Number} idx The index of the selected suggestion\n * \n * @emits CustomEvent 'this.select.done'\n * \n * @returns {Void}\n */\n select = (idx = 0) => { \n this.dom.input.value = typeof this.props.data[idx] === 'object' ? this.props.data[idx][this.configuration.propToMapAsValue] : this.props.data[idx];\n this.callbacks.onSelect(this.props.data[idx]);\n this.broadcaster.trigger(event.selectDone);\n }\n};","import { animation, searchExpression, theme } from './enums.js';\n\n/**\n * @description Represents the configuration for the Kompleter library.\n */\nexport class Configuration {\n /**\n * The type of animation for the element.\n * @type {string}\n */\n _animationType = animation.fadeIn\n \n /**\n * The duration of the animation in milliseconds.\n * @type {number}\n */\n _animationDuration = 500\n\n /**\n * Indicates whether multiple selections are allowed.\n * @type {boolean}\n * @private\n */\n _multiple = false\n \n /**\n * The theme for the kompletr options.\n * @type {string}\n */\n _theme = theme.light\n\n /**\n * Array containing the fields to be displayed.\n * @type {Array}\n * @private\n */\n _fieldsToDisplay = []\n\n /**\n * The maximum number of results to display.\n * @type {number}\n */\n _maxResults = 10\n\n /**\n * The character index from which querying should start.\n * @type {number}\n */\n _startQueriyngFromChar = 2\n\n /**\n * Represents the value of a property to be mapped.\n * @type {string}\n * @private\n */\n _propToMapAsValue = ''\n\n /**\n * The filter option used for filtering data.\n * Possible values are 'prefix', 'expression'.\n * @type {string}\n */\n _filterOn = searchExpression.prefix\n\n /**\n * Represents the cache value.\n * @type {number}\n * @private\n */\n _cache = 0\n\n /**\n * @description Type of animation between valid types\n */\n get animationType() {\n return this._animationType;\n }\n\n set animationType(value) {\n const valid = Object.keys(animation);\n if (!valid.includes(value)) {\n throw new Error(`animation.type should be one of ${valid.toString()}`);\n }\n this._animationType = value;\n }\n\n /**\n * @description Duration of some animation in ms. Default 500\n */\n get animationDuration() {\n return this._animationDuration;\n }\n\n set animationDuration(value) {\n if (isNaN(parseInt(value, 10))) {\n throw new Error(`animation.duration should be an integer`);\n }\n this._animationDuration = value;\n }\n\n /**\n * @description Enable / disable multiple choices\n */\n get multiple() {\n return this._multiple;\n }\n\n set multiple(value) {\n this._multiple = value;\n }\n\n /**\n * @description Display theme between light | dark\n */\n get theme() {\n return this._theme;\n }\n\n set theme(value) {\n const valid = Object.keys(theme);\n if (!valid.includes(value)) {\n throw new Error(`theme should be one of ${valid.toString()}, ${value} given`);\n }\n this._theme = value;\n }\n\n /**\n * @description Fields to display in each suggestion item\n */\n get fieldsToDisplay() {\n return this._fieldsToDisplay;\n }\n\n set fieldsToDisplay(value) {\n this._fieldsToDisplay = value;\n }\n \n /**\n * @description Maximum number of results to display as suggestions (can be different thant the number of results availables)\n */\n get maxResults() {\n return this._maxResults;\n }\n\n set maxResults(value) {\n this._maxResults = value;\n }\n\n /**\n * @description Input minimal value length before to fire research\n */\n get startQueriyngFromChar() {\n return this._startQueriyngFromChar;\n }\n\n set startQueriyngFromChar(value) {\n this._startQueriyngFromChar = value;\n }\n\n /**\n * @description Property to map as value\n */\n get propToMapAsValue() {\n return this._propToMapAsValue;\n }\n\n set propToMapAsValue(value) {\n this._propToMapAsValue = value;\n }\n\n /**\n * @description Apply filtering from the beginning of the word (prefix) or on the entire expression (expression)\n */\n get filterOn() {\n return this._filterOn;\n }\n\n set filterOn(value) {\n const valid = Object.keys(searchExpression);\n if (!valid.includes(value)) {\n throw new Error(`filterOn should be one of ${valid.toString()}, ${value} given`);\n }\n this._filterOn = value;\n }\n\n /**\n * @description Time life of the cache when data is retrieved from an API call\n */\n get cache() {\n return this._cache;\n }\n\n set cache(value) {\n if (isNaN(parseInt(value, 10))) {\n throw new Error(`cache should be an integer`);\n }\n this._cache = value;\n }\n\n constructor(options) {\n if (options === undefined) return;\n if (typeof options !== 'object') {\n throw new Error('options should be an object');\n };\n this._theme = options?.theme || this._theme;\n this._animationType = options?.animationType || this._animationType;\n this._animationDuration = options?.animationDuration || this._animationDuration;\n this._multiple = options?.multiple || this._multiple;\n this._fieldsToDisplay = options?.fieldsToDisplay || this._fieldsToDisplay;\n this._maxResults = options?.maxResults || this._maxResults;\n this._startQueriyngFromChar = options?.startQueriyngFromChar || this._startQueriyngFromChar;\n this._propToMapAsValue = options?.propToMapAsValue || this._propToMapAsValue;\n this._filterOn = options?.filterOn || this._filterOn;\n this._cache = options?.cache || this._cache;\n }\n}","import { event } from \"./enums.js\";\n\n/**\n * @description Kompletr simple caching mechanism implementation.\n * \n * @see https://developer.mozilla.org/en-US/docs/Web/API/Cache\n * @see https://web.dev/articles/cache-api-quick-guide\n */\nexport class Cache {\n\n /**\n * @description Cache name value\n */\n _name = null;\n\n /**\n * @description Cache timelife duration\n */\n _duration = null;\n \n /**\n * @description Broadcaster instance\n */\n _braodcaster = null;\n \n constructor(broadcaster, duration = 0, name = 'kompletr.cache') {\n if (!window.caches) {\n return false;\n }\n this._broadcaster = broadcaster;\n this._name = name;\n this._duration = duration;\n }\n\n /**\n * @description Retrieve the data stored in cache and dispatch event with\n * \n * @param {String} string Input value of the current request as string\n * @param {Function} done Callback function\n * \n * @emits CustomEvent 'kompltetr.request.done' { from, data }\n * @emits CustomEvent 'kompltetr.error' { error }\n * \n * @returns {Void}\n */\n get(string, done) {\n window.caches.open(this._name)\n .then(cache => {\n cache.match(string)\n .then(async (data) => {\n done(await data.json());\n });\n })\n .catch(e => {\n this._broadcaster.trigger(event.error, e);\n });\n }\n\n /**\n * @description Push data into the cache\n * \n * @param {Object} args { string, data }\n * \n * @emits CustomEvent 'kompltetr.error' { error }\n * \n * @returns {Void}\n */\n set({ string, data }) {\n window.caches.open(this._name)\n .then(cache => {\n const headers = new Headers();\n headers.set('Content-Type', 'application/json');\n headers.set('Cache-Control', `max-age=${this._duration}`);\n cache.put(`/${string}`, new Response(JSON.stringify(data), { headers }));\n })\n .catch(e => {\n this._broadcaster.trigger(event.error, e);\n });\n }\n\n /**\n * @description Check the cache validity regarding the current request and the cache timelife\n * \n * @param {String} string The current request value\n *\n * @returns {Promise}\n */\n async isValid(string) {\n try {\n const cache = await window.caches.open(this._name);\n const response = await cache.match(`/${string}`);\n if (!response) {\n return false;\n }\n return true;\n } catch(e) {\n this._broadcaster.trigger(event.error, e);\n }\n }\n};","import { event } from './enums.js';\n\n/**\n * Represents a Broadcaster that allows subscribing to and triggering events.\n */\nexport class Broadcaster {\n subscribers = [];\n\n constructor() {}\n\n /**\n * Subscribes to an event.\n * \n * @param {string} type - The type of event to subscribe to.\n * @param {Function} handler - The event handler function.\n * \n * @throws {Error} If the event type is not valid.\n */\n subscribe(type, handler) {\n if (!Object.values(event).includes(type)) {\n throw new Error(`Event should be one of ${Object.keys(event)}: ${type} given.`);\n }\n this.subscribers.push({ type, handler });\n }\n\n /**\n * Listens for an event on a specified element.\n * \n * @param {HTMLElement} element - The element to listen on.\n * @param {string} type - The type of event to listen for.\n * @param {Function} handler - The event handler function.\n */\n listen(element, type, handler) {\n element.addEventListener(type, handler);\n }\n\n /**\n * Triggers an event.\n * \n * @param {string} type - The type of event to trigger.\n * @param {Object} detail - Additional details to pass to the event handler.\n * \n * @throws {Error} If the event type is not valid.\n */\n trigger(type, detail = {}) {\n if (!Object.values(event).includes(type)) {\n throw new Error(`Event should be one of ${Object.keys(event)}: ${type} given.`);\n }\n \n this.subscribers\n .filter(subscriber => subscriber.type === type)\n .forEach(subscriber => subscriber.handler(detail));\n }\n}","/**\n * @description Dynamic properties of current Kompltr instance.\n */\nexport class Properties {\n\n /**\n * @description Data storage\n */\n _data = null\n\n get data() {\n return this._data;\n }\n\n set data(value) {\n if (!Array.isArray(value)) {\n throw new Error(`data must be an array (${value.toString()} given)`);\n }\n this._data = value;\n }\n\n /**\n * @description Position of the pointer inside the suggestions\n */\n _pointer = null\n\n get pointer() {\n return this._pointer;\n }\n\n set pointer(value) {\n if (isNaN(parseInt(value, 10))) {\n throw new Error(`pointer must be an integer (${value.toString()} given)`);\n }\n this._pointer = value;\n }\n\n /**\n * @description Previous input value\n */\n _previousValue = null\n\n get previousValue() {\n return this._previousValue;\n }\n\n set previousValue(value) {\n this._previousValue = value;\n }\n\n constructor(data = []) {\n this._data = data;\n }\n}","import { event } from './enums.js';\n\n/**\n * @description\n */\nexport class DOM {\n\n /**\n * @description Body tag\n */\n _body = null;\n\n get body() {\n return this._body;\n }\n\n set body(value) {\n this._body = value;\n }\n\n /**\n * @description Main input text\n */\n _input = null;\n\n get input() {\n return this._input;\n }\n\n set input(value) {\n if (input instanceof HTMLInputElement === false) {\n throw new Error(`input should be an HTMLInputElement instance: ${input} given.`);\n }\n this._input = value;\n }\n\n /**\n * @description HTMLElement in suggestions who's have the focus\n */\n _focused = null;\n\n get focused() {\n return this._focused;\n }\n\n set focused(value) {\n this._focused = value;\n }\n\n /**\n * @description HTMLElement results container\n */\n _result = null;\n\n get result() {\n return this._result;\n }\n\n set result(value) {\n this._result = value;\n }\n \n _broadcaster = null;\n\n // TODO: do better with hardcoded theme and classes\n // TODO: do bindings out of the constructor\n constructor(input, broadcaster, options = { theme: 'light' }) {\n this._body = document.getElementsByTagName('body')[0];\n \n this._input = input instanceof HTMLInputElement ? input : document.getElementById(input);\n this._input.setAttribute('class', `${this._input.getAttribute('class')} input--search`);\n \n this._result = this.build('div', [ { id: 'kpl-result' }, { class: 'form--search__result' } ]);\n\n this._input.parentElement.setAttribute('class', `${this._input.parentElement.getAttribute('class')} kompletr ${options.theme}`);\n this._input.parentElement.appendChild(this._result);\n\n\n this._broadcaster = broadcaster;\n }\n\n /**\n * @description Build an HTML element and set his attributes\n * \n * @param {String} element HTML tag to build\n * @param {Object[]} attributes Key / values pairs\n * \n * @returns {HTMLElement}\n */\n build(element, attributes = []) {\n const htmlElement = document.createElement(element);\n attributes.forEach(attribute => {\n htmlElement.setAttribute(Object.keys(attribute)[0], Object.values(attribute)[0]);\n });\n return htmlElement;\n };\n\n /**\n * @description Add / remove the focus on a HTMLElement\n * \n * @param {String} action add|remove\n *\n * @returns {Void}\n */\n focus(pointer, action) {\n if (!['add', 'remove'].includes(action)) {\n throw new Error('action should be one of [\"add\", \"remove]: ' + action + ' given.');\n }\n\n switch (action) {\n case 'add':\n this.focused = this.result.children[pointer];\n this.result.children[pointer].className += ' focus';\n break;\n case 'remove':\n this.focused = null;\n Array.from(this.result.children).forEach(result => {\n ((result) => {\n result.className = 'item--result';\n })(result)\n });\n break;\n }\n }\n\n /**\n * @description Display results according to the current input value / setup\n * \n * @returns {Void}\n */\n buildResults(data, fieldsToDisplay) {\n let html = '';\n\n if(data && data.length) {\n html = data\n .reduce((html, current) => {\n html += `
`;\n switch (typeof current.data) {\n case 'string':\n html += `${current.data}`;\n break;\n case 'object':\n let properties = Array.isArray(fieldsToDisplay) && fieldsToDisplay.length ? fieldsToDisplay: Object.keys(current.data);\n for(let j = 0; j < properties.length; j++) {\n html += `${current.data[properties[j]]}`;\n }\n break;\n }\n html += '
';\n return html;\n }, '');\n \n } else {\n html = '
Not found
';\n }\n\n this.result.innerHTML = html;\n this._broadcaster.trigger(event.domDone); // TODO here or in the handlers ?\n }\n}","import Kompletr from \"./kompletr.js\"\n\nimport { Configuration } from './configuration.js';\nimport { Cache } from './cache.js';\nimport { Broadcaster } from \"./broadcaster.js\";\nimport { Properties } from './properties.js';\nimport { DOM } from './dom.js';\n\n/**\n * Initializes the module with the provided data and options.\n *\n * @param {Object} options - The configuration options for the application.\n * @param {Object} data - The data used by the application.\n * @param {Function} onKeyup - The callback function to be executed on keyup event.\n * @param {Function} onSelect - The callback function to be executed on select event.\n * @param {Function} onError - The callback function to be executed on error event.\n * \n * @returns {void}\n */\nconst kompletr = function({ data, options, onKeyup, onSelect, onError }) {\n const broadcaster = new Broadcaster();\n\n const configuration = new Configuration(options);\n const properties = new Properties(data);\n\n const dom = new DOM(this, broadcaster, configuration);\n const cache = configuration.cache ? new Cache(broadcaster, configuration.cache) : null;\n \n new Kompletr({ configuration, properties, dom, cache, broadcaster, onKeyup, onSelect, onError });\n};\n\nwindow.HTMLInputElement.prototype.kompletr = kompletr;\n\nexport default kompletr;"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","animation","freeze","fadeIn","slideDown","error","domDone","dataDone","selectDone","origin","cache","callback","local","searchExpression","prefix","expression","theme","light","dark","Animation","constructor","element","display","duration","style","opacity","fade","value","parseFloat","requestAnimationFrame","fadeOut","slideUp","transitionProperty","transitionDuration","boxSizing","height","offsetHeight","overflow","paddingTop","paddingBottom","marginTop","marginBottom","window","setTimeout","removeProperty","console","log","getComputedStyle","animateBack","type","Kompletr","broadcaster","callbacks","configuration","dom","props","properties","onKeyup","onSelect","onError","this","subscribe","showResults","bindResults","closeTheShop","listen","input","suggest","body","assign","e","trigger","srcElement","result","animationType","animationDuration","resetPointer","pointer","stack","async","from","data","map","record","idx","filter","propToMapAsValue","filterOn","toLowerCase","lastIndexOf","set","string","buildResults","slice","maxResults","fieldsToDisplay","children","length","i","focused","select","id","startQueriyngFromChar","keyCode","navigate","previousValue","hydrate","isValid","focus","Configuration","_animationType","_animationDuration","_multiple","_theme","_fieldsToDisplay","_maxResults","_startQueriyngFromChar","_propToMapAsValue","_filterOn","_cache","valid","keys","includes","Error","toString","isNaN","parseInt","multiple","options","undefined","Cache","_name","_duration","_braodcaster","name","caches","_broadcaster","done","open","then","match","json","catch","headers","Headers","put","Response","JSON","stringify","Broadcaster","subscribers","handler","values","push","addEventListener","detail","subscriber","forEach","Properties","_data","Array","isArray","_pointer","_previousValue","DOM","_body","_input","HTMLInputElement","_focused","_result","document","getElementsByTagName","getElementById","setAttribute","getAttribute","build","class","parentElement","appendChild","attributes","htmlElement","createElement","attribute","action","className","html","reduce","current","j","innerHTML","kompletr"],"sourceRoot":""} \ No newline at end of file diff --git a/package.json b/package.json index 0a9098d..6a93c68 100755 --- a/package.json +++ b/package.json @@ -47,14 +47,14 @@ ], "license": "ISC", "scripts": { - "build": "mkdir -p dist && cp -r src/index.html dist/ & npm run build:css && npm run build:js", - "build:css": "node-sass ./src/sass/kompleter.scss ./dist/css/kompleter.min.css --output-style compressed", + "build": "mkdir -p dist && cp -r src/index.html dist/ & npm run build:css && npm run build:js && npm run build:demo", + "build:css": "node-sass ./src/sass/kompletr.scss ./dist/css/kompletr.min.css --output-style compressed", "build:js": "webpack", - "ci:api": "node ./cypress-api/index.js", - "ci:dev": "webpack serve --config ./webpack.config.dev.js", + "build:demo": "node-sass ./src/sass/kompletr.demo.scss ./demo/css/kompletr.demo.min.css --output-style compressed", + "ci:dev": "webpack serve", + "ci:e2e": "cypress run --browser chrome ./cypress", + "ci:cy": "cypress open", "ci:test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./test/**.spec.js -- --coverage", - "cypress:open": "cypress open", - "cypress:run": "cypress run --browser chrome ./cypress", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./test/**.spec.js", "dev": "webpack-dashboard -- webpack serve --hot --mode development --config ./webpack.config.js", "prod": "webpack-dashboard -- webpack --mode production --config ./webpack.config.prod.js", diff --git a/src/index.html b/src/index.html index 294b53b..14e9add 100644 --- a/src/index.html +++ b/src/index.html @@ -12,17 +12,13 @@ - - - - @@ -74,7 +70,7 @@

Kømpletr

headers.append('content-type', 'application/x-www-form-urlencoded'); headers.append('method', 'GET'); - fetch(`../cypress-api/data.json`, headers) + fetch(`./files/data.json`, headers) .then(result => result.json()) .then(data => { input.kompletr({ diff --git a/src/js/dom.js b/src/js/dom.js index eeca185..db9ebc5 100644 --- a/src/js/dom.js +++ b/src/js/dom.js @@ -127,25 +127,20 @@ export class DOM { * * @returns {Void} */ - focus(pointer, action) { - if (!['add', 'remove'].includes(action)) { - throw new Error('action should be one of ["add", "remove]: ' + action + ' given.'); + focus(pointer) { + if (isNaN(parseInt(pointer, 10)) || pointer < 0 || pointer > this.result.children.length - 1) { + throw new Error('pointer should be a valid integer in the result lenght range: ' + pointer + ' given.'); } - switch (action) { - case 'add': - this.focused = this.result.children[pointer]; - this.result.children[pointer].className += ` ${this._classes.focus}`; - break; - case 'remove': - this.focused = null; - Array.from(this.result.children).forEach(result => { - ((result) => { - result.className = this._classes.result; - })(result) - }); - break; - } + this.focused = null; + Array.from(this.result.children).forEach(result => { + ((result) => { + result.className = this._classes.result; + })(result) + }); + + this.focused = this.result.children[pointer]; + this.result.children[pointer].className += ` ${this._classes.focus}`; } /** diff --git a/src/js/kompletr.js b/src/js/kompletr.js index d38aad4..83b1d2a 100644 --- a/src/js/kompletr.js +++ b/src/js/kompletr.js @@ -211,8 +211,7 @@ export default class Kompletr { this.props.pointer++; } - this.dom.focus(this.props.pointer, 'remove'); // TODO: check if we can do better like in one step - this.dom.focus(this.props.pointer, 'add'); + this.dom.focus(this.props.pointer); } /** diff --git a/src/sass/kompleter.scss b/src/sass/kompletr.demo.scss old mode 100755 new mode 100644 similarity index 100% rename from src/sass/kompleter.scss rename to src/sass/kompletr.demo.scss diff --git a/src/sass/kompletr.scss b/src/sass/kompletr.scss new file mode 100755 index 0000000..05369b4 --- /dev/null +++ b/src/sass/kompletr.scss @@ -0,0 +1,154 @@ + +@import 'mixins'; +@import 'variables'; + +//// +// Module code +//// +/// +.kompletr { + &.form--search { + width: 30%; + position: relative; + margin: 0 auto; + @media (max-width: 480px) { + width: 90%; + } + } + + .input--search, + .item--result { + font-family: $font-base; + font-size: 100%; + } + + .input--search { + display: block; + box-sizing: border-box; + margin: 0 auto; + padding: 15px 10px; + width: 100%; + min-width: 240px; + max-width: 600px; + height: auto; + font-size: 1.2rem; + line-height: 1.5; + border: none; + &:focus { + border: none; + outline: none; + } + } + + .form--search__result { + position: absolute; + margin: 0; + width: 100%; + } + + .item--result { + box-sizing: border-box; + width: 100%; + padding: 15px; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + border-left: none; + border-right: none; + + &:last-child { + border-bottom: none; + } + + &:hover, &.focus { + cursor: pointer; + @include transition(0.2s ease-in-out); + } + + & .item--data { + flex: 50%; + + &:nth-child(even) { + text-align: right; + } + + &:nth-child(0) { + font-weight: 600; + } + + &:nth-child(n+2) { + font-weight: normal; + } + } + } + + &.light { + .input--search { + color: $color-4; + background: $color-1; + } + + ::placeholder { + color: $color-2; + } + + .item--result { + color: $color-6; + border-bottom: 1px dashed $color-3; + @include backdrop(16px, 180%, 0.75); + + &:hover, &.focus { + @include backdrop(26px, 120%, 0.50); + + & .item--data:nth-child(n+2) { + color: $color-6; + } + } + + & .item--data { + &:nth-child(0) { + color: $color-6; + } + + &:nth-child(n+2) { + color: $color-4; + } + } + } + } + + &.dark { + .input--search { + color: $color-1; + background: $color-6; + } + + ::placeholder { + color: $color-2; + } + + .item--result { + color: $color-1; + border-bottom: 1px dashed $color-4; + @include backdrop(16px, 180%, 0.75, 'dark'); + + &:hover, &.focus { + @include backdrop(26px, 120%, 0.50, 'dark'); + + & .item--data:nth-child(n+2) { + color: $color-1; + } + } + + & .item--data { + &:nth-child(0) { + color: $color-1; + } + + &:nth-child(n+2) { + color: $color-4; + } + } + } + } +} \ No newline at end of file diff --git a/test/dom.spec.js b/test/dom.spec.js index 304a99f..d3c3dea 100644 --- a/test/dom.spec.js +++ b/test/dom.spec.js @@ -32,6 +32,14 @@ describe('DOM', () => { expect(dom.input).toBeDefined(); expect(dom.focused).toBeDefined(); }); + + it('should throws error when attempt to set input with something else than HTMLInputElement', () => { + try { + dom.input = 'test'; + } catch(e) { + expect(e.message).toBe('input should be an HTMLInputElement instance: test given.'); + } + }); }); describe('::build', () => { @@ -46,17 +54,24 @@ describe('DOM', () => { describe('::focus', () => { it('should manage adding and removing focus', () => { dom.result.appendChild(document.createElement('div')); - dom.focus(0, 'add'); + dom.result.appendChild(document.createElement('div')); + dom.result.appendChild(document.createElement('div')); + dom.focus(1); expect(dom.focused).toBeInstanceOf(HTMLElement); expect(dom.focused.className).toContain('focus'); - dom.focus(0, 'remove'); - expect(dom.focused).toBeNull(); - expect(dom.result.firstChild.className).not.toContain('focus'); + }); + + it('should throws error when the pointer is out of range', () => { + try { + dom.focus(-1); + } catch(e) { + expect(e.message).toBe('pointer should be a valid integer in the result lenght range: -1 given.'); + } }); }); describe('::buildResults', () => { - it('should build well formed DOM with suggestions results', () => { + it('should build well formed DOM with suggestions results as strings', () => { const data = [{ idx: '1', data: 'test' }]; dom.buildResults(data); expect(dom.result.firstChild).toBeInstanceOf(HTMLElement); @@ -66,5 +81,22 @@ describe('DOM', () => { expect(dom.result.firstChild.firstChild.textContent).toBe('test'); expect(broadcaster.trigger).toHaveBeenCalled(); }); + + it('should build well formed DOM with suggestions results as object', () => { + const data = [{ idx: '1', data: { prop: 'test'} }]; + dom.buildResults(data, ['prop']); + expect(dom.result.firstChild).toBeInstanceOf(HTMLElement); + expect(dom.result.firstChild.id).toBe('1'); + expect(dom.result.firstChild.className).toBe('item--result'); + expect(dom.result.firstChild.firstChild.className).toBe('item--data'); + expect(dom.result.firstChild.firstChild.textContent).toBe('test'); + expect(broadcaster.trigger).toHaveBeenCalled(); + }); + + it('should build with a not found when no result', () => { + dom.buildResults([]); + expect(dom.result.innerHTML).toContain('Not found'); + expect(broadcaster.trigger).toHaveBeenCalled(); + }); }); }); \ No newline at end of file diff --git a/test/kompletr.spec.js b/test/kompletr.spec.js index ddf7d79..8fa0a5a 100644 --- a/test/kompletr.spec.js +++ b/test/kompletr.spec.js @@ -312,8 +312,7 @@ describe('Kompletr', () => { instance.props.pointer = 2; instance.dom.result = { children: [1, 2, 3, 4, 5] }; instance.navigate(38); - expect(spy).toHaveBeenCalledWith(1, 'add'); - expect(spy).toHaveBeenCalledWith(1, 'remove'); + expect(spy).toHaveBeenCalledWith(1); }); }); diff --git a/webpack.config.dev.js b/webpack.config.dev.js deleted file mode 100644 index 697bb65..0000000 --- a/webpack.config.dev.js +++ /dev/null @@ -1,35 +0,0 @@ -import path from 'path'; -import * as url from 'url'; - -const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); - -export default { - entry: './src/js/index.js', - devtool: "source-map", - mode: "development", - module: { - rules: [ - { - test: /\.html$/i, - loader: "html-loader", - }, - { - test: /\.js$/i, - loader: "esbuild-loader", - }, - ], - }, - devServer: { - client: { - logging: 'log', - overlay: true, - }, - static: { - directory: path.join(__dirname, './dist'), - }, - compress: true, - port: 9000, - historyApiFallback: true, - liveReload: true, - }, -};