vanilla-icon-picker-virtualized
Version:
Icon picker - Vanilla JS icon picker with virtualization
3 lines • 14.3 kB
JavaScript
/*! Icon Picker 0.0.7 MIT | https://github.com/AppoloDev/icon-picker */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.IconPicker=t():e.IconPicker=t()}(self,()=>(()=>{"use strict";var e={d:(t,i)=>{for(var s in i)e.o(i,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:i[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};function i(e,t,i,s){Array.isArray(t)||(t=[t]),Array.isArray(i)||(i=[i]);for(const n of t)for(const t of i)n[e](t,s);return[].slice.call(arguments,1)}e.d(t,{default:()=>_});const s=i.bind(null,"addEventListener"),n=i.bind(null,"removeEventListener");function r(e){return e&&"object"==typeof e&&!Array.isArray(e)}function o(e,t){let i=Object.assign({},e);return r(e)&&r(t)&&Object.keys(t).forEach(s=>{r(t[s])?s in e?i[s]=o(e[s],t[s]):Object.assign(i,{[s]:t[s]}):Object.assign(i,{[s]:t[s]})}),i}function a(e,t){var i;return function(){var s=arguments,n=this;clearTimeout(i),i=setTimeout(function(){e.apply(n,s)},t)}}function l(e,t){return Object.keys(e).find(i=>e[i]===t)}class h{constructor({container:e,items:t,renderItem:i,i18nEmpty:s="No results",estimateItemSize:n={width:42,height:42},gaps:r={x:6,y:6},bufferRows:o=3}){if(!e)throw new Error("VirtualIconGrid: container is required");if(!i)throw new Error("VirtualIconGrid: renderItem is required");this.container=e,this.items=Array.isArray(t)?t.slice():[],this.renderItem=i,this.i18nEmpty=s,this.estimate=n,this.bufferRows=Math.max(0,0|o),this._query="",this._indexed=[],this._filtered=[],this._cols=1,this._itemW=this.estimate.width,this._itemH=this.estimate.height,this._gapX=r.x,this._gapY=r.y,this._mounted=!1,this._onScroll=this._onScroll.bind(this),this._onResize=this._onResize.bind(this),this._inner=document.createElement("div"),this._inner.className="vs-inner",this._emptyEl=document.createElement("div"),this._emptyEl.className="is-empty",this._emptyEl.textContent=this.i18nEmpty,this._lastRange={start:-1,end:-1},this._resizeObs=null,this._buildIndex(),this._filtered=this.items}mount(){if(!this._mounted){for(this._mounted=!0;this.container.firstChild;)this.container.removeChild(this.container.firstChild);this.container.classList.add("virtual-scroll"),this.container.appendChild(this._inner),this._measureItem(),this._computeLayout(),this._updateInnerHeight(),this._renderWindow(),this._toggleEmptyMessage(),this.container.addEventListener("scroll",this._onScroll,{passive:!0}),"ResizeObserver"in window?(this._resizeObs=new ResizeObserver(this._onResize),this._resizeObs.observe(this.container)):window.addEventListener("resize",this._onResize)}}destroy(){this._mounted&&(this._mounted=!1,this.container.removeEventListener("scroll",this._onScroll),this._resizeObs?(this._resizeObs.disconnect(),this._resizeObs=null):window.removeEventListener("resize",this._onResize),this._inner.replaceChildren(),this._emptyEl&&this._emptyEl.parentNode&&this._emptyEl.remove())}setItems(e){this.items=Array.isArray(e)?e.slice():[],this._buildIndex(),this._applyFilter()}setQuery(e){const t=(e||"").toLowerCase();t!==this._query&&(this._query=t,this._applyFilter())}_buildIndex(){this._indexed=this.items.map(e=>({value:e.value,text:[e.value,...e.categories||[]].join(" ").toLowerCase(),ref:e}))}_applyFilter(){if(this._query){const e=this._query;this._filtered=this._indexed.filter(t=>t.text.includes(e)).map(e=>e.ref)}else this._filtered=this.items;this._toggleEmptyMessage(),this._updateInnerHeight(),this._lastRange.start=-1,this._renderWindow()}_toggleEmptyMessage(){this._filtered.length>0?this._emptyEl.parentNode&&this._emptyEl.remove():this._emptyEl.parentNode||this.container.appendChild(this._emptyEl)}_measureItem(){const e=this.items[0]||{value:"sample"},t=this.renderItem(e);t.style.position="absolute",t.style.visibility="hidden",t.classList.add("vs-item"),this._itemW=this.estimate.width,this._itemH=this.estimate.height,t.remove()}_computeLayout(){const e=this.container.clientWidth||1,t=this._itemW+this._gapX,i=Math.max(1,Math.floor((e+this._gapX)/t));this._cols=i}_totalRows(){return Math.ceil(this._filtered.length/this._cols)}_updateInnerHeight(){const e=this._totalRows()*(this._itemH+this._gapY)-this._gapY;this._inner.style.height=Math.max(0,e).toString()+"px";const t=this._inner.parentElement.offsetWidth-this._inner.parentElement.clientWidth;this._inner.parentElement.style.width=Math.max(this._cols*(this._itemW+this._gapX)-this._gapX+t)+"px"}_onResize(){const e=this._cols;this._computeLayout(),this._cols!==e?(this._updateInnerHeight(),this._lastRange.start=-1,this._renderWindow()):this._renderWindow()}_onScroll(){this._renderWindow()}_renderWindow(){if(!this._filtered.length)return this._inner.replaceChildren(),void(this._lastRange={start:-1,end:-1});const e=this.container.scrollTop,t=this.container.clientHeight,i=this._itemH+this._gapY,s=Math.max(0,Math.floor(e/i)-this.bufferRows),n=Math.floor((e+t)/i)+this.bufferRows,r=s*this._cols,o=Math.min(this._filtered.length,(n+1)*this._cols);if(r===this._lastRange.start&&o===this._lastRange.end)return;this._lastRange={start:r,end:o};const a=document.createDocumentFragment();for(let e=r;e<o;e++){const t=this._filtered[e];if(!t)continue;const i=Math.floor(e/this._cols),s=e%this._cols*(this._itemW+this._gapX),n=i*(this._itemH+this._gapY),r=this.renderItem(t);r.classList.add("vs-item"),r.style.transform=`translate(${s}px, ${n}px)`,r.style.width=this._itemW+"px",r.style.height=this._itemH+"px",a.appendChild(r)}this._inner.replaceChildren(a)}}const c=e=>{const{theme:t,i18n:i,closeOnSelect:s}=e,n=function(e){const t=(e,t)=>{const i=e.getAttribute(t);return e.removeAttribute(t),i},i=(e,s={})=>{const n=t(e,"data-element");n&&(s[n]=e);for(const n of Array.from(e.children)){const e=t(n,"data-interaction");e&&e&&(s[e]=n),i(n,s)}return s};return i(function(e){if(window.DOMParser)return(new DOMParser).parseFromString(e,"text/html").body.firstElementChild;const t=document.createElement("div");return t.innerHTML=e,t.firstElementChild}(e))}(`\n <div class="icon-picker-modal" tabindex="-1" data-theme="${t}" data-element="modal" aria-modal="true" aria-labelledby="Icon picker modal" role="dialog">\n <div class="icon-picker-modal__dialog">\n <div class="icon-picker-modal__header" data-element="header">\n ${"string"==typeof i["text:title"]&&""!==i["text:title"]?`<h2>${i["text:title"]}</h2>`:""}\n \n <button class="icon-picker-modal--close" aria-label="Close" data-interaction="close">\n <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke-width="1.5">\n <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6.758 17.243 12.001 12m5.243-5.243L12 12m0 0L6.758 6.757M12.001 12l5.243 5.243"/>\n </svg>\n </button>\n </div>\n \n <input placeholder="${i["input:placeholder"]}" class="icon-picker-modal__search" aria-label="${i["input:placeholder"]}" data-interaction="search">\n \n <div class="icon-picker-modal__content" data-element="content"></div>\n \n ${s?"":`\n <div class="icon-picker-modal__footer" data-element="footer">\n <button type="button" class="picker-save" data-interaction="save">${i["btn:save"]}</button>\n </div>`}\n </div>\n </div>\n `);return t.includes("bootstrap")&&(n.save?.classList.add("btn","btn-primary"),n.search.classList.add("form-control")),n};const d="https://raw.githubusercontent.com/iconify/icon-sets/master/json",u={"Material Design Icons":{key:"mdi",prefix:"mdi mdi-",url:`${d}/mdi.json`},"FontAwesome Brands 6":{key:"fa6-brands",prefix:"fab fa-",url:`${d}/fa6-brands.json`},"FontAwesome Solid 6":{key:"fa6-solid",prefix:"fas fa-",url:`${d}/fa6-solid.json`},"FontAwesome Regular 6":{key:"fa6-regular",prefix:"far fa-",url:`${d}/fa6-regular.json`},Iconoir:{key:"iconoir",prefix:"iconoir-",url:`${d}/iconoir.json`}};function m(e){let t=Object.create({});return Array.isArray(e)&&e.forEach(e=>{!function(e){return u.hasOwnProperty(e)}(e)?e.key&&(t[e.key]=e):t[u[e].key]=u[e]}),t}class _{virtualIconGrid=null;availableIcons=[];static DEFAULT_OPTIONS={theme:"default",closeOnSelect:!0,defaultValue:null,iconSource:[],i18n:{"input:placeholder":"Search icon…","text:title":"Select icon","text:empty":"No results found…","text:loading":"Loading…","btn:save":"Save"},itemSize:50,iconSize:32};_eventListener={select:[],save:[],show:[],clear:[],hide:[],loaded:[]};constructor(e,t={}){this.options=o(_.DEFAULT_OPTIONS,t),this.element=e,this.iconsLoading=!0,this._preBuild(),this.element&&this.options.iconSource.length>0?(this._binEvents(),this._renderdIcons(),this._createModal()):this._catchError("iconSourceMissing")}_preBuild(){var e;this.element=(e=this.element)instanceof HTMLElement?e:"string"==typeof e?document.querySelector(e):null,this.root=c(this.options),!Array.isArray(this.options.iconSource)&&this.options.iconSource.length>0&&(this.options.iconSource=[this.options.iconSource]),this.virtualIconGrid=this.virtualIconGrid||new h({container:this.root.content,items:this.availableIcons??[],renderItem:e=>this.renderItem(e),i18nEmpty:this.options.i18n["text:empty"],estimateItemSize:{width:this.options.itemSize,height:this.options.itemSize},bufferRows:4})}renderItem(e){const t=document.createElement("button");return t.type="button",t.className=`icon-element ${e.value}`,t.title=e.value,t.setAttribute("aria-label",e.value),t.dataset.value=e.inputValue,t.innerHTML=e.body,t.addEventListener("click",t=>e.onSelect&&e.onSelect(t)),t}ensureVirtualMounted=()=>{requestAnimationFrame(()=>{this.virtualIconGrid._mounted?this.virtualIconGrid._onResize&&this.virtualIconGrid._onResize():this.virtualIconGrid.mount();const e=(this.root.search?.value||"").trim().toLowerCase();this.virtualIconGrid.setQuery(e),this.virtualIconGrid.setItems(this.availableIcons)})};_binEvents(){const{options:e,root:t,element:i,virtualIconGrid:n}=this,r=a(e=>{const t=(e.target.value||"").trim().toLowerCase();n&&n.setQuery(t)},120);this._eventBindings=[s(i,"click",()=>{this.show(),this.ensureVirtualMounted()}),s(t.close,"click",()=>this.hide()),s(t.modal,"click",e=>{e.target===t.modal&&this.hide()}),s(t.search,"keyup",a(r,250))],e.closeOnSelect||this._eventBindings.push(s(t.save,"click",()=>this._onSave()))}hide(){return!!this.isOpen()&&(this.root.modal.classList.remove("is-visible"),this._emit("hide"),this)}show(){return!this.isOpen()&&(this.root.modal.classList.add("is-visible"),this._emit("show"),this)}clear(){this.initialized&&this.currentlySelectName&&(this.currentlySelectName=null,this._emit("clear"))}isOpen(){return this.root.modal.classList.contains("is-visible")}iconsLoaded(){return!this.loadingState}destroy(e=!0){this.initialized=!1,this._eventBindings.forEach(e=>n(...e)),e&&Object.keys(this).forEach(e=>delete this[e])}_emit(e,...t){this._eventListener[e].forEach(e=>e(...t,this))}on(e,t){return void 0!==this._eventListener[e]&&(this._eventListener[e].push(t),this)}off(e,t){const i=this._eventListener[e]||[],s=i.indexOf(t);return~s&&i.splice(s,1),this}_createModal(){document.body.appendChild(this.root.modal),this.initialized=!0}_onSave(){this._setValueInput(),this.hide(),this._emit("save",this.emitValues)}async _renderdIcons(){const{root:e,options:t}=this;let i=null,s=null,n=null;if(this.availableIcons=[],e.content.innerHTML=`<div class="is-loading">${t.i18n["text:loading"]}</div>`,(await this._getIcons()).forEach(e=>{let r=e.iconFormat?e.iconFormat:"svg";for(const[o,a]of Object.entries(e.icons)){const h=document.createElement("button");if(h.title=o,h.className=`icon-element ${o}`,h.dataset.value=e.prefix+o,e.categories&&Object.entries(e.categories).length>0){n=[];for(const[t]of Object.entries(e.categories))e.categories[t].includes(o)&&(n.length>0?n.push(t.toLowerCase()):n=[t.toLowerCase()])}let c;if(e.chars&&(h.dataset.unicode=l(e.chars,o)),"i"!==r&&a.body)if("markup"===r){let e=document.createElement("template");e.innerHTML=a.body,c=e.content}else c=document.createElementNS("http://www.w3.org/2000/svg","svg"),c.setAttribute("height",this.options.iconSize.toString()),c.setAttribute("width",this.options.iconSize.toString()),c.setAttribute("viewBox",`0 0 ${a.width?a.width:e.width} ${a.height?a.height:e.height}`),c.innerHTML=a.body;else c=document.createElement("i"),c.setAttribute("class",h.dataset.value);this.availableIcons.push({inputValue:e.prefix+o,value:o,body:c.outerHTML,...n?.length>0&&{categories:n},onSelect:n=>{this.currentlySelectName!==n.currentTarget.firstChild.className&&(n.currentTarget.classList.add("is-selected"),s=n.currentTarget,this.currentlySelectName=s.dataset.value,this.SVGString=c.outerHTML,this.emitValues={name:o,value:this.currentlySelectName,svg:this.SVGString},e.chars&&(this.emitValues.unicode=c.dataset.unicode),this._emit("select",this.emitValues)),i&&i.classList.remove("is-selected"),t.closeOnSelect&&this._onSave(),i=s}})}}),t.defaultValue||this.element.value){let e=document.querySelector(`[data-value="${t.defaultValue?t.defaultValue:this.element.value}"]`)?document.querySelector(`[data-value="${t.defaultValue?t.defaultValue:this.element.value}"]`):document.querySelector(`.${t.defaultValue?t.defaultValue:this.element.value}`),s=e?.dataset.value??"";e?.classList.add("is-selected"),i=e,this.currentlySelectName=s,this.element.value||this._setValueInput()}this.ensureVirtualMounted();const r=e.content.querySelector(".is-loading");r&&r.remove(),this.iconsLoading=!1,this._emit("loaded")}async _getIcons(){const{options:e}=this,t=[];let i=m(e.iconSource);for(const e of Object.values(i))t.push(e.url);return await Promise.all(t.map(e=>fetch(e).then(e=>e.json()))).then(e=>(e.forEach(e=>{i.hasOwnProperty(e.prefix)&&(e.prefix=i[e.prefix].prefix)}),e))}_catchError(e){if("iconSourceMissing"===e)throw Error("No icon source was found.")}_setValueInput(e=this.currentlySelectName){const{element:t}=this;t instanceof HTMLInputElement&&this.currentlySelectName&&(t.value=e)}}return t=t.default})());
//# sourceMappingURL=icon-picker.min.map